/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-12
 * V4.0
 */
package com.jphenix.share.tools;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;

import com.jphenix.driver.json.Json;
import com.jphenix.driver.nodehandler.FNodeHandler;
import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.share.lang.SBoolean;
import com.jphenix.share.lang.SInteger;
import com.jphenix.share.lang.SListMap;
import com.jphenix.share.lang.SLong;
import com.jphenix.share.lang.SString;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.share.util.StringUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.exceptions.HttpException;
import com.jphenix.standard.log.ILog;
import com.jphenix.standard.servlet.IResponse;
import com.jphenix.standard.viewhandler.INodeHandler;
import com.jphenix.standard.viewhandler.IViewHandler;

/**
 * 通过HTTP协议调用接口处理类
 * 
 * 如果调用的接口采用  PATCH 方式，即 Method=PATCH
 * 在 headerMap 中put x-http-method-override 值为 PATCH
 * 
 * 2018-05-21 原来抛异常用的都是MsgException，但是这样做无法将目标返回码抛出去，于是改用新增的HttpException
 * 2018-06-14 为POST方法增加传入Map参数
 * 2018-06-15 修改了 createSSLContext 方法，原本默认加密类型为TLS，现改为TLSv1.2，如果需要指定加密类型，需要在执行前设置线程会话HTTPCALL_SSL_CONTEXT_TYPE的值
 * 2018-07-17 优化了代码，避免重复获取http返回状态码
 * 2018-08-20 增加了除了GET POST 以外的其它访问HTTP方法
 * 2018-10-18 传入提交参数支持Map格式，会自动将Map对象转换为Post字符串
 * 2019-04-16 去掉了一块重复代码
 * 2019-04-27 修改了重定向时拼装URL错误
 * 2019-06-15 将createSSLContext() 方法开放出来
 * 2019-07-20 下载文件方法，下载时使用临时文件名，下载后再重命名
 * 2019-08-03 增加了从其它服务器下载文件并直接输出（代理下载）
 * 2019-08-12 去掉了无用的重复代码
 * 2019-08-17 优化了代码
 * 2019-08-19 在下载文件方法downloadFile中，增加了指定下载后文件名的功能（下载路径字符串前加#，说明下载路径中包含了下载文件名）
 *            优化了输出流处理，增加了缓存
 * 2019-08-29 修改了downloadFile方法指定下载文件名时，存在的错误
 * 2019-09-10 增加了一些方法
 * 2019-09-11 增加了调用http协议，传入输入流、输出流参数的方法
 * 2019-09-18 修改了传入输入流、输出流调用目标地址的方法
 * 2019-09-20 修改了传入输入流、输出流调用目标地址的方法
 * 2019-10-21 将几处HTTP请求报错抛出的异常改为HttpException
 * 2019-11-22 修改了一个重大错误，导致提交数据对方获取不到
 * 2019-11-29 修改了调用目标URL，并将返回信息直接输出到中转得Response对象中。避免304从缓存获取信息
 * 2019-12-02 修改了调用目标URL，并将返回信息直接输出到中转得Response对象中。设置Set-Cookie会话信息错误
 * 2020-03-02 增加了高级调用方法（支持通过代理服务器调用）
 * 2020-03-16 完善了抛异常时输出的信息
 * 2020-04-03 修改了上传文件方法中，提交参数时参数值中存在等号时，解析错误的问题
 * 2020-05-25 新增了一个专门用于代理的http调用方法
 * 2020-05-26 修改了个别方法重定向判断错误，以及重定向拼装URL错误
 * 2020-07-15 修改了直接代理下载文件时，源头返回信息中不包含文件大小时，导致文件下载失败的问题
 * 2020-07-17 修改了swapCookie 处理 set-cookie 时的错误
 * 2020-07-18 增加了代理调用目标请求方法
 * 2020-07-27 增加了强制修改下载文件类型头信息
 * 2020-07-28 修改了getMime方法中，重复扩展名的处理
 * 2020-07-31 重新修改了代理调用目标方法
 * 2020-12-29 修改了传入request，response参数，在返回内容时，通过调用目标请求返回的类型中获取的编码代码进行解码
 * 2021-03-17 在Content-Type增加了json类型
 * 
 * @author 马宝刚 
 * 2009-3-25  下午02:07:54
 */
@ClassInfo({"2021-03-17 21:14","通过HTTP协议调用接口处理类"})
public class HttpCall {

  protected final static int BUFFER_SIZE = 2048; //缓存大小

  /**
   * 将待提交的内容中的保留字符变成转意字符
   * @author 马宝刚
   * @param value 待处理的字符
   * @return      处理后的字符
   * 2009-3-30  下午03:59:30
   */
  public static String getFixValue(String value) {
    if (value==null) {
      return "";
    }
    return value
      .replaceAll("\\%","%25")
      .replaceAll("\\=","%3D")
      .replaceAll("\\+","%2B")
      .replaceAll("\\ ","+")
      .replaceAll("\\\n","%0D")
      .replaceAll("\\#","%23")
      .replaceAll("\\'","%27")
      .replaceAll("\\,","%2C")
      .replaceAll("\\\"","%22")
      .replaceAll("\\;","%3B")
      .replaceAll("\\.","%2E")
      .replaceAll("\\-","%2D")
      .replaceAll("\\:","%3A")
      .replaceAll("\\/","%2F")
      .replaceAll("\\\r","")
      .replaceAll("\\\t","%09")
      .replaceAll("\\&","%26")
      .replaceAll("\\?","%3F")
      .replaceAll("\\!","%21")
      .replaceAll("\\<","%3C")
      .replaceAll("\\>","%3E")
      .replaceAll("\\\\","%5C");
  }
  
  /**
   * 调用URL并获取返回值
   * @author 马宝刚
   * @param sUrl         访问路径
   * @param params       传入参数容器(XML内容)
   * @param sendEncoding 发送信息编码
   * @param resEncoding  返回内容编码格式
   * @param contentType  0 html  1xml   2json
   * @return             返回信息
   * @throws Exception 异常
   * 2011-9-27 下午02:07:53
   */
    public static String call(
      String sUrl,
      String params,
      String sendEncoding,
      String resEncoding,
      int    contentType) throws Exception {
    return call(sUrl,params,sendEncoding,resEncoding,contentType, null);
  }
    
    /**
     * 调用URL并获取返回值
     * @param sUrl       访问路径
     * @return           返回信息
     * @throws Exception 异常
     * 2016年9月28日
     * @author MBG
     */
    public static String call(String sUrl) throws Exception {
      return call(sUrl,null,"UTF-8","UTF-8",0, null);
    }
    
    /**
     * 调用URL并获取返回值
     * @param sUrl        访问路径
     * @param params      传入参数容器(或XML内容，或Json内容)
     * @param contentType 0 html  1xml   2json
     * @return            返回信息
     * @throws Exception  异常
     * 2016年9月28日
     * @author MBG
     */
    public static String call(
      String sUrl,String params,int contentType) throws Exception {
      return call(sUrl,params,"UTF-8","UTF-8",contentType, null);
    }
    
  /**
   * 调用URL并获取返回值 （默认提交参数为html格式 即  key1=value1&key2=value2）
   * @author 马宝刚
   * @param sUrl         访问路径
   * @param params       传入参数容器(XML内容)
   * @param sendEncoding 发送信息编码
   * @param resEncoding  返回内容编码格式
   * @return             返回信息
   * @throws Exception 异常
   * 2011-9-27 下午02:07:53
   */
    public static String call(
      String sUrl,
      String params,
      String sendEncoding,
      String resEncoding) throws Exception {
    return call(sUrl,params,sendEncoding,resEncoding,0, null);
  }
    
  /**
   * 调用URL并获取返回值 （默认提交参数为xml格式 即  <key1>value1</key1><key2>value2</key2>）
   * @author 马宝刚
   * @param sUrl         访问路径
   * @param xmlContent   要提交的xml内容
   * @param sendEncoding 发送信息编码
   * @param resEncoding  返回内容编码格式
   * @return             返回信息
   * @throws Exception   异常
   * 2011-9-27 下午02:07:53
   */
    public static String postXml(
      String sUrl,
      String xmlContent,
      String sendEncoding,
      String resEncoding) throws Exception {
    return call(sUrl,xmlContent,sendEncoding,resEncoding,1, null);
  }
    
  /**
   * 调用URL并获取返回值 （默认提交参数为json格式 即  {key1:value1,key2:value2}）
   * @author 马宝刚
   * @param sUrl         访问路径
   * @param jsonContent  要提交的json内容
   * @param sendEncoding 发送信息编码
   * @param resEncoding  返回内容编码格式
   * @return             返回信息
   * @throws Exception   异常
   * 2011-9-27 下午02:07:53
   */
  public static String postJson(
      String sUrl,
      String jsonContent,
      String sendEncoding,
      String resEncoding) throws Exception {
    return call(sUrl,jsonContent,sendEncoding,resEncoding,2, null);
  }
    
    /**
     * 提交Json对象到目标服务器并返回json对象
     * @param sUrl       目标url
     * @param jsonPara   提交的json数据
     * @param session    会话信息容器
     * @return           json对象
     * @throws Exception 异常
     * 2016年6月8日
     * @author MBG
     */
    public static Json postJson(
      String sUrl,Json jsonPara,Map<String,String> session) throws Exception {
      //调用目标服务器并获取返回值
      String res = call(sUrl,jsonPara==null?"":jsonPara.toString(),"UTF-8","UTF-8",2,session);
      return new Json(res);
    }
    
    /**
     * 调用Json接口
     * @param sUrl       目标url
     * @param json       传入参数，可以为字符串，可以为json对象
     * @param headerMap  头信息
     * @return           返回json对象
     * @throws Exception 异常
     * 2016年1月27日
     * @author 马宝刚
     */
    @SuppressWarnings("rawtypes")
    public static Json call(String sUrl,Object data,Map<String,String> headerMap) throws Exception {
        String params; //传入参数
        int contentType = 0; //类型
        if(data==null) {
            params = "";
        }else if(data instanceof String) {
            params = (String)data;
        }else {
            if(data instanceof Json) {
                contentType = 2;
                params = data.toString();
            }else if(data instanceof IViewHandler) {
                contentType = 1;
                params = data.toString();
            }else if(data instanceof Map) {
              params = StringUtil.getParaStringFromMap((Map)data);
            }else {
              params = data.toString();
            }
        }
        return new Json(call(sUrl,params,"UTF-8","UTF-8",contentType,headerMap));
    }
    
    /**
     * 提交数据并获取返回值
     * @param url       目标url
     * @return 返回值
     * @throws Exception 异常
     * 2017年4月6日
     * @author MBG
     */
    public static String post(String url) throws Exception {
        return post(url,null,null);
    }
    
    /**
     * 提交数据并获取返回值
     * @param url        目标url
     * @param data       数据：  字符串（a=11&bb=22）  Json对象   IViewHandler对象（xml）
     * @return           返回值
     * @throws Exception 异常
     * 2017年4月6日
     * @author MBG
     */
    public static String post(String url,Object data) throws Exception {
        return post(url,data,null);
    }
    
    /**
     * 提交数据并获取返回值
     * @param url        目标url
     * @param data       数据：  字符串（a=11&bb=22）  Json对象   IViewHandler对象（xml）
     * @param headerMap  头信息（用来保持会话）
     * @return           返回值
     * @throws Exception 异常
     * 2017年4月6日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    public static String post(
    String url,Object data,Map<String,String> headerMap) throws Exception {
        String params; //传入参数
        int contentType = 0; //类型
        if(data==null) {
            params = null;
        }else if(data instanceof String) {
            params = (String)data;
        }else {
            if(data instanceof Json) {
                contentType = 2;
                params = data.toString();
            }else if(data instanceof IViewHandler) {
                contentType = 1;
                params = data.toString();
            }else if(data instanceof Map) {
              params = StringUtil.getParaStringFromMap((Map)data);
            }else {
              params = data.toString();
            }
        }
        return call(url,params,"UTF-8","UTF-8",contentType,headerMap);
    }
    
    /**
     * 调用Json接口
     * @param sUrl       目标url
     * @param json       传入参数，可以为字符串，可以为json对象
     * @return           返回json对象
     * @throws Exception 异常
     * 2016年1月27日
     * @author 马宝刚
     */
    public static Json call(String sUrl,Object json) throws Exception {
      return call(sUrl,json,null);
    }
  
  /**
   * 执行上传文件
   * @author 刘虻
   * 2009-4-3上午09:16:30
   * @param sUrl         目标路径
   * @param params       传入参数
   * @param uploadName   上传文件对象名 即 <file name="就是这个名" />
   * @param fileNameList 文件名序列  (可以为中文名，即上传前的名字）
   * @param filePathList 对应文件路径序列 文件全路径，带文件名 即落地后的文件名。为避免文件名乱码，落地后的文件名都是英文名
   * @param sendEncoding 发送信息编码
   * @param resEncoding  返回信息编码
   * @param headerMap    页面头信息容器
   * @param              返回结果页面内容
   * @throws Exception   执行发生异常
   */
  public static String upload(
      String             sUrl,
      String             params,
      List<String>       uploadNameList,
      List<String>       fileNameList,
      List<String>       filePathList,
      String             sendEncoding,
      String             resEncoding,
      Map<String,String> headerMap) throws Exception {
    if(headerMap==null) {
      headerMap = new HashMap<String,String>();
    }
    if(uploadNameList==null) {
      uploadNameList = new ArrayList<String>();
    }
    if(sendEncoding==null || sendEncoding.length()<1) {
      sendEncoding = "UTF-8";
    }
    //构建分割符
    String boundary = "---------------------------" + System.currentTimeMillis();
    String boundaryInContent = "--" + boundary;
    
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
    //读取字节数
    int readCount = 0;
        ByteArrayOutputStream bos = null; //字节输出流
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,"multipart/form-data; boundary="+boundary,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            //构建输出信息流
            OutputStream out = urlc.getOutputStream();
            int    point;  //参数名参数值分隔符位置
            String key;    //参数名
            String val;    //参数值
            if(params!=null) {
                //处理提交参数
                String[] paras = params.split("&");
                for (int i = 0; i < paras.length; i++) {
                    if (paras[i] == null) {
                        continue;
                    }
                    // 分割参数
                    point = paras[i].indexOf("=");
                    if(point<0) {
                      continue;
                    }
                    key = paras[i].substring(0,point);
                    val = paras[i].substring(point+1);
                    out.write(boundaryInContent.getBytes());
                    out.write('\r');
                    out.write('\n');
                    out.write("Content-Disposition: form-data; name=\"".getBytes());
                    out.write(key.getBytes(sendEncoding));
                    out.write("\"\r\n\r\n".getBytes());
                    out.write(val.getBytes(sendEncoding));
                    out.write('\r');
                    out.write('\n');
                }
            }
            if (filePathList!=null) {
                String fileName; //文件名
                String filePath; //获取文件路径
                String uploadName; //上传对象名
              for(int i=0;i<filePathList.size();i++) {
                filePath = filePathList.get(i);
                if (filePath==null || filePath.length()<1) {
                  continue;
                }
                //构建上传文件对象
                File uFile = new File(filePath);
                if(!uFile.exists()) {
                  continue;
                }
                fileName = fileNameList.get(i);
                if (fileName==null || fileName.length()<1) {
                    fileName = uFile.getName();
                }
                uploadName = null;
                if(uploadNameList.size()>i) {
                  uploadName = uploadNameList.get(i);
                }
                if(uploadName==null || uploadName.length()<1) {
                  uploadName = "uploads";
                }
                
                out.write(boundaryInContent.getBytes());
                out.write('\r');
                out.write('\n');
                
                out.write("Content-Disposition: form-data; name=\"".getBytes());
                out.write(uploadName.getBytes());
                out.write("\"; filename=\"".getBytes());
                out.write(fileName.getBytes(sendEncoding));
                //该处理论上用一个回车符就够了，但实际上只用一个回车符
                out.write("\"\r\nContent-Type: application/octet-stream\r\n\r\n".getBytes());
                //构建文件读入流
                FileInputStream fis = new FileInputStream(uFile);
              //获取读取字节数
                    while ((readCount=fis.read(readBytes))!=-1) {
                      out.write(readBytes,0,readCount);
                      if (readCount<BUFFER_SIZE) {
                        break;
                      }
                    }
                    fis.close();
                    //上传后的文件有时比源文件少一个字节，增加下面一句后，丢失的字节恢复了，不是0
                    //out.write(0);  //后来发现跟浏览器上传的格式不同，不需要加这个语句
                    out.write('\r');
                    out.write('\n');
              }
            }
            out.write(boundaryInContent.getBytes());
            out.write("--\r\n\r\n".getBytes());
            out.flush();
            out.close(); 
            
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP  || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向地址
              String redirUrl = SString.valueOf(headerMap.get("Location"));
              if(!redirUrl.toLowerCase().startsWith("http")) {
                redirUrl = getBaseUrl(sUrl)+redirUrl;
              }
                return upload(redirUrl,null,uploadNameList,fileNameList,filePathList,sendEncoding,resEncoding,headerMap);
            }
            //获取信息流
            is = urlc.getInputStream();
            //建立字节输出流
            bos = new ByteArrayOutputStream();
            while ((readCount = is.read(readBytes))!=-1) {
                bos.write(readBytes,0,readCount);
            }
            if(resEncoding!=null && resEncoding.length()>0) {
                return bos.toString(resEncoding);
            }
            return bos.toString();
        } catch (IOException e) {
            //e.printStackTrace();  已经抛出去，没必要在这里打日志
            throw new HttpException(sUrl,params,statusCode,statusMsg,e);
        }finally {
            try {
              is.close();
            } catch (Exception e2) {}
            try {
              bos.close();
            }catch(Exception e2) {}
        }
  }
        
  /**
   * 执行上传文件
   * @author 刘虻
   * 2009-4-3上午09:16:30
   * @param sUrl 目标路径
   * @param params       传入参数
   * @param uploadName   上传文件对象名 即 <file name="就是这个名" />
   * @param fileName     文件名         文件名序列  (可以为中文名，即上传前的名字）
   * @param filePath     文件绝对路径   文件全路径，带文件名 即落地后的文件名。为避免文件名乱码，落地后的文件名都是英文名
   * @param sendEncoding 发送信息编码
   * @param resEncoding  返回信息编码
   * @param contentType  文件类型
   * @param headerMap    页面头信息容器
   * @param              返回结果页面内容
   * @throws Exception   执行发生异常
   */
  public static String upload(
      String             sUrl,
      String             params,
      String             uploadName,
      String             fileName,
      String             filePath,
      String             sendEncoding,
      String             resEncoding,
      Map<String,String> headerMap) throws Exception {
    if(headerMap==null) {
      headerMap = new HashMap<String,String>();
    }
    if(uploadName==null || uploadName.length()<1) {
      uploadName = "uploads";
    }
    if(filePath==null || filePath.length()<1) {
      System.err.println("--------------------------upload-return null filePath is null");
      return "";
    }
    if(sendEncoding==null || sendEncoding.length()<1) {
      sendEncoding = "UTF-8";
    }
    if(fileName==null) {
      fileName = SFilesUtil.getFileName(filePath);
    }
    //构建分割符
    String boundary = "---------------------------" + System.currentTimeMillis();
    String boundaryInContent = "--" + boundary;

    InputStream is = null; //读入信息流
    byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
    //读取字节数
    int readCount = 0;
    ByteArrayOutputStream bos = null; //字节输出流
    int statusCode = 0; //返回状态码
    String statusMsg = ""; //返回状态信息
    try {
      //构造URL
      URL url = new URL(sUrl);
      //构造链接
      URLConnection urlc = url.openConnection();
      if(urlc instanceof HttpsURLConnection) {
        //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
        ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
          @Override
                    public boolean verify(String hostname, SSLSession session) {
            return true;
          }
        });
        ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
      }
      //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
      ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
      setHeaderInfo(urlc,headerMap,"multipart/form-data; boundary="+boundary,0,null); //设置头信息
      urlc.setDoOutput(true);   
      urlc.setUseCaches(false);
      //构建输出信息流
      OutputStream out = urlc.getOutputStream();
            int    point;  //参数名参数值分隔符位置
            String key;    //参数名
            String val;    //参数值
            if(params!=null) {
                //处理提交参数
                String[] paras = params.split("&");
                for (int i=0; i<paras.length; i++) {
                    if (paras[i] == null) {
                        continue;
                    }
                    // 分割参数
                    point = paras[i].indexOf("=");
                    if(point<0) {
                      continue;
                    }
                    key = paras[i].substring(0,point);
                    val = paras[i].substring(point+1);
                    out.write(boundaryInContent.getBytes());
                    out.write('\r');
                    out.write('\n');
                    out.write("Content-Disposition: form-data; name=\"".getBytes());
                    out.write(key.getBytes(sendEncoding));
                    out.write("\"\r\n\r\n".getBytes());
                    out.write(val.getBytes(sendEncoding));
                    out.write('\r');
                    out.write('\n');
                }
            }
      //构建上传文件对象
      File uFile = new File(filePath);
      if(!uFile.exists()) {
        System.err.println("--------------------------upload-return null file not found["+filePath+"]");
        return "";
      }
      out.write(boundaryInContent.getBytes());
      out.write('\r');
      out.write('\n');

      out.write("Content-Disposition: form-data; name=\"".getBytes());
      out.write(uploadName.getBytes());
      out.write("\"; filename=\"".getBytes());
      out.write(fileName.getBytes(sendEncoding));
      //该处理论上用一个回车符就够了，但实际上只用一个回车符
      out.write("\"\r\nContent-Type: application/octet-stream\r\n\r\n".getBytes());
      //构建文件读入流
      FileInputStream fis = new FileInputStream(uFile);
      //获取读取字节数
      while ((readCount=fis.read(readBytes))!=-1) {
        out.write(readBytes,0,readCount);
        if (readCount<BUFFER_SIZE) {
          break;
        }
      }
      fis.close();
      //上传后的文件有时比源文件少一个字节，增加下面一句后，丢失的字节恢复了，不是0
      //out.write(0);  //后来发现跟浏览器上传的格式不同，不需要加这个语句
      out.write('\r');
      out.write('\n');

      out.write(boundaryInContent.getBytes());
      out.write("--\r\n\r\n".getBytes());
      out.flush();
      out.close(); 

      statusCode = ((HttpURLConnection)urlc).getResponseCode();
      statusMsg = ((HttpURLConnection)urlc).getResponseMessage();

      fixHeadInfo(urlc,headerMap); //处理 头信息
      //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
      if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP  || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向地址
              String redirUrl = SString.valueOf(headerMap.get("Location"));
              if(!redirUrl.toLowerCase().startsWith("http")) {
                redirUrl = getBaseUrl(sUrl)+redirUrl;
              }
        return upload(redirUrl,uploadName,null,fileName,filePath,sendEncoding,resEncoding,headerMap);
      }
      //获取信息流
      is = urlc.getInputStream();
      //建立字节输出流
      bos = new ByteArrayOutputStream();
      while ((readCount = is.read(readBytes))!=-1) {
        bos.write(readBytes,0,readCount);
      }
      if(resEncoding!=null && resEncoding.length()>0) {
        return bos.toString(resEncoding);
      }
      return bos.toString();
    } catch (IOException e) {
      //e.printStackTrace(); 已经抛出异常，没必要在这里打印日志
      throw new HttpException(sUrl,params,statusCode,statusMsg,e);
    }finally {
      try {
        is.close();
      } catch (Exception e2) {}
      try {
        bos.close();
      }catch(Exception e2) {}
    }
  }
      
  /*
   * POST /upload HTTP/1.1
   * Host: 10.77.3.44:7002
   * Connection: keep-alive
   * Content-Length: 675
   * Cache-Control: max-age=0
   * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,;q=0.8
   * Origin: null
   * User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36
   * Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryMD7xQLQQpPZpb61B
   * Accept-Encoding: gzip,deflate
   * Accept-Language: zh-CN,zh;q=0.8
   * Cookie: CNZZDATA1254165172=1947474705-1422935129-%7C1422935129; JSESSIONID=PX1426050518437-14260505184370-964
   * 
   * ------WebKitFormBoundaryMD7xQLQQpPZpb61B
   * Content-Disposition: form-data; name="serviceId"
   * 
   * esb.ygt.fileupload
   * ------WebKitFormBoundaryMD7xQLQQpPZpb61B
   * Content-Disposition: form-data; name="fqr"
   * 
   * 0
   * ------WebKitFormBoundaryMD7xQLQQpPZpb61B
   * Content-Disposition: form-data; name="sessionId"
   * 
   * admin4ea16b7b-7cf2-46aa-bac3-69d05ea4536a
     * -----WebKitFormBoundaryMD7xQLQQpPZpb61B
   * Content-Disposition: form-data; name="filename"
   * 
   * a.txt
   * ------WebKitFormBoundaryMD7xQLQQpPZpb61B
   * Content-Disposition: form-data; name="file"; filename="a.txt"
   * Content-Type: text/plain
   * 
   * @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   * ------WebKitFormBoundaryMD7xQLQQpPZpb61B--
   * 
   * boundary 分隔符标识，比真正的分隔符的开头，少两个减号，在数据末尾的分隔符后面，还需要多两个减号
   */
  
  /**
   * 讲下载的文件直接输出
   * @param sUrl 目标URL
   * @param params 传入参数
   * @param encoding 发送编码格式
   * @param headerMap 报文头
   * @param os 输出流（接收到文件时直接通过输出流输出）
   * @throws Exception 异常
   * 2015年6月16日
   * @author 马宝刚
   */
  @SuppressWarnings("deprecation")
  public static void writeDownloadFile(
          String             sUrl,
          String             params,
          String             encoding,
          Map<String,String> headerMap,
          OutputStream       os) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,0,encoding); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (params!=null) {
                //构建输出信息流
                DataOutputStream out = 
                    new DataOutputStream(urlc.getOutputStream());   
                out.writeBytes(params);   
                out.flush();
                out.close();   
            }
            
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向地址
              String redirUrl = SString.valueOf(headerMap.get("Location"));
              if(!redirUrl.toLowerCase().startsWith("http")) {
                redirUrl = getBaseUrl(sUrl)+redirUrl;
              }
                writeDownloadFile(redirUrl,null,encoding,headerMap,os);
            }
            //获取信息流
            is = urlc.getInputStream();
            String fileName = null; //保存文件名
            //没有结束上限，在循环体中退出，大于索引数时不会抛错
            for(int i=0;;i++) {
                //头主键
                String headerKey = urlc.getHeaderFieldKey(i);
                //头值
                String headerValue = urlc.getHeaderField(i);
                if (headerKey==null && headerValue==null) {
                    //头结束
                    break;
                }
                if (headerKey==null || headerValue==null) {
                    continue;
                }
                if ("content-disposition".equalsIgnoreCase(headerKey)) {
                    //截取信息段
                    String[] values = headerValue.split(";\\s*");
                    if (values!=null) {
                        for(int j=0;j<values.length;j++) {
                            if (values[j]!=null 
                                    && values[j].toLowerCase().indexOf("filename")>-1) {
                                //取值分隔符
                                int vPoint = values[j].indexOf("=");
                                if (vPoint<0) {
                                    continue;
                                }
                                //判断单引号的位置
                                int sPoint = values[j].indexOf("\'");
                                //判断双引号的位置
                                int dPoint = values[j].indexOf("\"");
                                
                                if (sPoint<0 && dPoint<0) {
                                    //没有字符串标识符
                                    fileName = values[j].substring(vPoint+1).trim();
                                    break;
                                }else if(sPoint>-1 && dPoint<0) {
                                    //用双引号作为标识符
                                    //获取末尾标识符位置
                                    int lPoint = values[j].lastIndexOf("\'");
                                    if (lPoint<0) {
                                        continue;
                                    }
                                    fileName = values[j].substring(sPoint+1,lPoint).trim();
                                    break;
                                }else if(dPoint>-1 && sPoint<0) {
                                    //用单引号作为标识符
                                    //获取末尾标识符位置
                                    int lPoint = values[j].lastIndexOf("\"");
                                    if (lPoint<0) {
                                        continue;
                                    }
                                    fileName = values[j].substring(dPoint+1,lPoint).trim();
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            if (fileName==null) {
                //获取URL末尾动作名
                fileName = sUrl.substring(sUrl.lastIndexOf("/")+1);
                int lastPoint = fileName.indexOf("?"); //动作结束标识
                if (lastPoint>-1) {
                    fileName = fileName.substring(0,lastPoint);
                }
                //转换编码格式
                if(encoding==null) {
                    fileName =URLDecoder.decode(fileName);
                }else {
                    fileName =URLDecoder.decode(fileName,encoding);
                }
            }else {
                if(encoding!=null) {
                    fileName = new String(fileName.getBytes(StandardCharsets.ISO_8859_1),encoding);
                }
            }
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
                os.write(readBytes,0,readCount);
            }
            return;
        } catch (IOException e) {
            //e.printStackTrace(); 已经抛出异常，没必要在这里打印日志
            throw new HttpException(sUrl,params,statusCode,statusMsg,e);
        }finally {
            try {
                is.close();
            } catch (Exception e2) {}
            try {
                os.flush();
            }catch(Exception e2) {}
            try {
                os.close();
            }catch(Exception e3) {}
        }
    
    }
  
  /**
   * 执行下载文件并返回文件读入流
   * @param sUrl       目标URL
   * @param params     提交参数   格式：aa=11&bb=22&cc=33
   * @param encoding   提交参数编码格式
   * @param headerMap  提交头信息（比如包含会话主键）
   * @return           需要下载文件的读入流
   * @throws Exception 异常
   * 2017年6月22日
   * @author MBG
   */
  public static InputStream download(
      String             sUrl,
      String             params,
      String             encoding,
      Map<String,String> headerMap) throws Exception {
    if(headerMap == null) {
      headerMap = new HashMap<String, String>();
    }
    int statusCode = 0; // 返回状态码
    String statusMsg = ""; // 返回状态信息
    try {
      // 构造URL
      URL url = new URL(sUrl);
      // 构造链接
      URLConnection urlc = url.openConnection();
      if (urlc instanceof HttpsURLConnection) {
        // 通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
        ((HttpsURLConnection) urlc).setHostnameVerifier(new HostnameVerifier() {
          @Override
          public boolean verify(String hostname, SSLSession session) {
            return true;
          }
        });
        ((HttpsURLConnection) urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
      }
      // 设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
      ((HttpURLConnection) urlc).setInstanceFollowRedirects(false);
      setHeaderInfo(urlc, headerMap, null, 0, encoding); // 设置头信息
      urlc.setDoOutput(true);
      urlc.setUseCaches(false);
      if (params != null) {
        // 构建输出信息流
        DataOutputStream out = new DataOutputStream(urlc.getOutputStream());
        out.writeBytes(params);
        out.flush();
        out.close();
      }
      statusCode = ((HttpURLConnection) urlc).getResponseCode();
      statusMsg = ((HttpURLConnection) urlc).getResponseMessage();
      fixHeadInfo(urlc, headerMap); // 处理 头信息
      // 如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
      if (statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM) {
        // 重定向地址
        String redirUrl = SString.valueOf(headerMap.get("Location"));
        if (!redirUrl.toLowerCase().startsWith("http")) {
          redirUrl = getBaseUrl(sUrl) + redirUrl;
        }
        return download(redirUrl, null, encoding, headerMap);
      }
      // 获取信息流
      return urlc.getInputStream();
    } catch (IOException e) {
      throw new HttpException(sUrl, params, statusCode, statusMsg, e);
    }
  }
  
  /**
   * 从另外服务器中下载文件并直接输出
   * @param sUrl          目标服务器URL
   * @param params        传入参数
   * @param encoding      提交参数编码格式
   * @param headerMap     报文头
   * @param response      当前服务器输出流
   * @param inPage        下载文件是否在浏览器中打开
   * @throws Exception    异常
   * 2019年8月3日
   * @author MBG
   */
  public static void downloadResponse(
      String               sUrl
      ,String              params
      ,String              encoding
      ,Map<String,String>  headerMap
      ,HttpServletResponse response
      ,boolean             inPage) throws Exception {
    downloadResponse(sUrl,params,encoding,null,headerMap,response,inPage);
  }
  
  /**
   * 从另外服务器中下载文件并直接输出
   * @param sUrl             目标服务器URL
   * @param params           传入参数
   * @param encoding         提交参数编码格式
   * @param fileNameEncoding 下载的文件名原编码
   * @param headerMap        报文头
   * @param response         当前服务器输出流
   * @param inPage           下载文件是否在浏览器中打开
   * @throws Exception       异常
   * 2019年8月3日
   * @author MBG
   */
  public static void downloadResponse(
      String              sUrl,
      String              params,
      String              encoding,
      String              fileNameEncoding,
      Map<String,String>  headerMap,
      HttpServletResponse response,
      boolean             inPage) throws Exception {
    if(headerMap==null) {
      headerMap = new HashMap<String,String>();
    }
    InputStream is = null; //读入信息流
    int statusCode = 0; //返回状态码
    //构造URL
    URL url = new URL(sUrl);
    //构造链接
    URLConnection urlc = url.openConnection();
    if(urlc instanceof HttpsURLConnection) {
      //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
      ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
        @Override
                public boolean verify(String hostname, SSLSession session) {
          return true;
        }
      });
      ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
    }
    //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
    ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
    setHeaderInfo(urlc,headerMap,null,0,encoding); //设置头信息
    urlc.setDoOutput(true);   
    urlc.setUseCaches(false);
    if (params!=null) {
      //构建输出信息流
      DataOutputStream out = 
          new DataOutputStream(urlc.getOutputStream());   
      out.writeBytes(params);   
      out.flush();
      out.close();   
    }
    statusCode = ((HttpURLConnection)urlc).getResponseCode();
    fixHeadInfo(urlc,headerMap); //处理 头信息
    //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
    if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
      //重定向地址
      String redirUrl = SString.valueOf(headerMap.get("Location"));
      if(!redirUrl.toLowerCase().startsWith("http")) {
        redirUrl = getBaseUrl(sUrl)+redirUrl;
      }
      downloadResponse(redirUrl,null,encoding,headerMap,response,inPage);
    }
    //获取信息流
    is = urlc.getInputStream();
    int    fileLenth = 0;    //保存文件大小
    String fileName  = null; //保存文件名
    //没有结束上限，在循环体中退出，大于索引数时不会抛错
    for(int i=0;;i++) {
      //头主键
      String headerKey = urlc.getHeaderFieldKey(i);
      //头值
      String headerValue = urlc.getHeaderField(i);
      if (headerKey==null && headerValue==null) {
        //头结束
        break;
      }
      if (headerKey==null || headerValue==null) {
        continue;
      }
      if("content-length".equalsIgnoreCase(headerKey)) {
        //设置文件大小
        fileLenth = SInteger.valueOf(headerValue);
        continue;
      }
      if ("content-disposition".equalsIgnoreCase(headerKey)) {
        //截取信息段
        String[] values = headerValue.split(";\\s*");
        if (values!=null) {
          for(int j=0;j<values.length;j++) {
            if (values[j]!=null 
                && values[j].toLowerCase().indexOf("filename")>-1) {
              //取值分隔符
              int vPoint = values[j].indexOf("=");
              if (vPoint<0) {
                continue;
              }
              //判断单引号的位置
              int sPoint = values[j].indexOf("\'");
              //判断双引号的位置
              int dPoint = values[j].indexOf("\"");

              if (sPoint<0 && dPoint<0) {
                //没有字符串标识符
                fileName = values[j].substring(vPoint+1).trim();
                break;
              }else if(sPoint>-1 && dPoint<0) {
                //用双引号作为标识符
                //获取末尾标识符位置
                int lPoint = values[j].lastIndexOf("\'");
                if (lPoint<0) {
                  lPoint = values[j].length();
                }
                fileName = values[j].substring(sPoint+1,lPoint).trim();
                break;
              }else if(dPoint>-1 && sPoint<0) {
                //用单引号作为标识符
                //获取末尾标识符位置
                int lPoint = values[j].lastIndexOf("\"");
                if (lPoint<0) {
                  lPoint = values[j].length();
                }
                fileName = values[j].substring(dPoint+1,lPoint).trim();
                break;
              }
            }
          }
        }
      }
    }
    
    if(fileName==null) {
      //没有文件名
      fileName = "";
    }else if(fileNameEncoding!=null && fileNameEncoding.length()>0) {
      //注意：有的时候还是需要做转码的，比如在微信中下载，如果不转码就是乱码
      fileName = new String(fileName.getBytes(StandardCharsets.ISO_8859_1),fileNameEncoding);
      fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
    }
    
    /*
    //在实际运行时发现，根本不需要转码，直接输出即可
    }else if(fileNameFromEncoding!=null && fileNameFromEncoding.length()>0 
        && fileNameToEncoding!=null && fileNameToEncoding.length()>0) {
      //文件名转码
      fileName = new String(fileName.getBytes(fileNameToEncoding),fileNameToEncoding);
    }
    */
    
    //重置一下缓存否则WebLogic会报错
    try {
      response.resetBuffer();
    }catch(Exception e) {}
    OutputStream os = null; //输出流
    try {
      os = response.getOutputStream(); //获取输出流
      //缓存数组
      byte[] bufferBytes = new byte[BUFFER_SIZE];

      if(fileName.length()>0) {
        response.setHeader(
            "Content-Disposition"
            ,(inPage?"inline":"attachment")+";filename=\""+fileName+"\"");
        //设置下载类型
        String contentType = getMime(SFilesUtil.getFileExtName(fileName));
        response.setContentType(contentType);
      }
      int readByteCount; //本次读取字节数
      if(fileLenth>0) {
        int readCount = 0; //读取字节数
        response.setContentLength(fileLenth);
        while(readCount<fileLenth){
          //获取本次读取字节数
          readByteCount = is.read(bufferBytes,0,BUFFER_SIZE);
          readCount += readByteCount; //累加读取字节
          //输出页面
          os.write(bufferBytes,0,readByteCount);
        }
      }else {
        //返回信息中不包含文件长度
        while ((readByteCount=is.read(bufferBytes))!=-1) {
          //输出页面
          os.write(bufferBytes,0,readByteCount);
        }
      }
    }catch(Exception e) {
      //忽略客户端放弃下载异常
      return;
    }finally {
      try {
        is.close();
      }catch(Exception e) {}
      try {
        os.flush();
      }catch(Exception e) {}
      try {
        os.close();
      }catch(Exception e) {}
    }
  }
  
  /**
   * 获取下载文件
   * @author 刘虻
   * 2009-4-2下午07:33:49
   * @param sUrl        下载url
   * @param params      提交参数字符串
   * @param encoding    路径编码格式
   * @param objFilePath 报文文件根路径 （注意：如果路径中包含文件名，在路径开头加上#)
   * @param headerMap   页面头信息容器
   * @return            文件保存后的路径
   * @throws Exception 执行发生异常
   */
  public static String downloadFile(
      String             sUrl,
      String             params,
      String             encoding,
      String             objFilePath,
      Map<String,String> headerMap) throws Exception {
    if(headerMap==null) {
      headerMap = new HashMap<String,String>();
    }
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        //构建目标文件
        File saveFile = null;
        //文件输出流
        FileOutputStream fos = null;
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,0,encoding); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (params!=null) {
              //构建输出信息流
              DataOutputStream out = 
                new DataOutputStream(urlc.getOutputStream());   
              out.writeBytes(params);   
              out.flush();
              out.close();   
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向地址
              String redirUrl = SString.valueOf(headerMap.get("Location"));
              if(!redirUrl.toLowerCase().startsWith("http")) {
                redirUrl = getBaseUrl(sUrl)+redirUrl;
              }
              return downloadFile(redirUrl,null,encoding,objFilePath,headerMap);
            }
            //获取信息流
            is = urlc.getInputStream();
            String fileName = null; //保存文件名
          //没有结束上限，在循环体中退出，大于索引数时不会抛错
          for(int i=0;;i++) {
            //头主键
            String headerKey = urlc.getHeaderFieldKey(i);
            //头值
            String headerValue = urlc.getHeaderField(i);
            if (headerKey==null && headerValue==null) {
              //头结束
              break;
            }
            if (headerKey==null || headerValue==null) {
              continue;
            }
            if ("content-disposition".equalsIgnoreCase(headerKey)) {
              //截取信息段
              String[] values = headerValue.split(";\\s*");
              if (values!=null) {
                for(int j=0;j<values.length;j++) {
                  if (values[j]!=null 
                      && values[j].toLowerCase().indexOf("filename")>-1) {
                    //取值分隔符
                    int vPoint = values[j].indexOf("=");
                    if (vPoint<0) {
                      continue;
                    }
                    //判断单引号的位置
                    int sPoint = values[j].indexOf("\'");
                    //判断双引号的位置
                    int dPoint = values[j].indexOf("\"");
                    
                    if (sPoint<0 && dPoint<0) {
                      //没有字符串标识符
                      fileName = values[j].substring(vPoint+1).trim();
                      break;
                    }else if(sPoint>-1 && dPoint<0) {
                      //用双引号作为标识符
                      //获取末尾标识符位置
                      int lPoint = values[j].lastIndexOf("\'");
                      if (lPoint<0) {
                        continue;
                      }
                      fileName = values[j].substring(sPoint+1,lPoint).trim();
                      break;
                    }else if(dPoint>-1 && sPoint<0) {
                      //用单引号作为标识符
                      //获取末尾标识符位置
                      int lPoint = values[j].lastIndexOf("\"");
                      if (lPoint<0) {
                        continue;
                      }
                      fileName = values[j].substring(dPoint+1,lPoint).trim();
                      break;
                    }
                  }
                }
              }
            }
          }
          if(objFilePath.startsWith("#")) {
            saveFile = SFilesUtil.createFile(objFilePath.substring(1));
          }else {
              if (fileName==null) {
                //获取URL末尾动作名
                fileName = sUrl.substring(sUrl.lastIndexOf("/")+1);
                int lastPoint = fileName.indexOf("?"); //动作结束标识
                if (lastPoint>-1) {
                  fileName = fileName.substring(0,lastPoint);
                }
                //转换编码格式
                if(encoding!=null) {
                  fileName =URLDecoder.decode(fileName,encoding);
                }
              }else {
                if(encoding!=null) {
                  fileName = new String(fileName.getBytes(StandardCharsets.ISO_8859_1),encoding);
                }
              }
              if(objFilePath.endsWith("?@")) {
                objFilePath = objFilePath.substring(0,objFilePath.length()-2)+"."+fixMime(headerMap.get("Content-Type"));
                fileName = SFilesUtil.getFileName(objFilePath);
                objFilePath = SFilesUtil.getFilePath(objFilePath);
              }else if (objFilePath.endsWith("/") || objFilePath.endsWith("\\")) {
                //不做处理
              }else {
                objFilePath += "/";
              }
            saveFile = SFilesUtil.createFile(objFilePath+fileName);
          }
            //建立字节输出流
            fos = new FileOutputStream(saveFile,false);
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
              fos.write(readBytes,0,readCount);
            }
            return saveFile.getPath();
        } catch (IOException e) {
          e.printStackTrace();
          saveFile.delete();
          System.err.print("[err] Error:HttpCall.downloadFile Exception URL:["+sUrl
                +"] params:["+params+"] statusCode:["+statusCode+"] statusMsg:["+statusMsg+"] e:["+e);
          throw new HttpException(sUrl,params,statusCode,statusMsg,e);
        }finally {
            try {
              is.close();
            } catch (Exception e2) {}
            try {
              fos.close();
            }catch(Exception e2) {}
        }
  }
  
  /**
   * 构建无需私钥的上下文对象
   * @return 上下文对象
   * 2015年1月20日
   * @author 马宝刚
   */
  public static SSLContext createSSLContext() {
    SSLContext sslcontext = null; // 构建返回值
    try {
      // 先从线程服务中获取本次调用目标URL需要用到的加密类型（以前是TLS）
      String type = ThreadSession.getString("HTTPCALL_SSL_CONTEXT_TYPE");
      if (type.length() < 1) {
        type = "TLS"; // 默认为最新的加密类型 TLSv1.2
      }
      sslcontext = SSLContext.getInstance(type);
      sslcontext.init(null, new TrustManager[] { new X509TrustManager() {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
          return new X509Certificate[0];
        }
        @Override
        public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
        }
        @Override
        public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
        }
      } }, null);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (KeyManagementException e) {
      e.printStackTrace();
    }
    SSLContext.setDefault(sslcontext);
    return sslcontext;
  }

  /**
   * 调用URL并获取返回值
   * @author 马宝刚
   * @param sUrl         访问路径
   * @param paraMap      传入参数容器(XML内容)
   * @param sendEncoding 发送信息编码
   * @param resEncoding  返回内容编码格式
   * @param contentType  0html  1xml  2json
   * @param headerMap    头信息容器(可以同步会话，获取头信息)
   * @return             返回信息
   * @throws Exception   执行发生异常
   * 2009-3-25  下午02:25:34
   */
    public static String call(
        String             sUrl,
        String             params,
        String             sendEncoding,
        String             resEncoding,
        int                contentType,
        Map<String,String> headerMap) throws Exception {
      if(headerMap==null) {
        headerMap = new HashMap<String,String>();
      }
        ByteArrayOutputStream bos = null; //字节输出流
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:["+params+"] sendEncoding:["+sendEncoding+"] resEncoding:["+resEncoding+"]");
          }
          
          //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,contentType,sendEncoding); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (params!=null && params.length()>0) {
              //构建输出信息流
              DataOutputStream out = 
                new DataOutputStream(urlc.getOutputStream());   
              if(sendEncoding!=null && sendEncoding.length()>0) {
                out.write(params.getBytes(sendEncoding));
              }else {
                out.writeBytes(params);
              }
              out.flush();
              out.close();   
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向URL
              String goUrl = SString.valueOf(headerMap.get("Location"));
              if(goUrl.indexOf("://")<0) {
                goUrl = getBaseUrl(sUrl)+goUrl;
              }
              if(log!=null) {
                log.log("HttpCall SendRedirectURL:["+goUrl+"]");
              }
              return call(goUrl,null,sendEncoding,resEncoding,contentType,headerMap);
            }
            //获取信息流
            is = urlc.getInputStream();
            //建立字节输出流
            bos = new ByteArrayOutputStream();
            
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
              bos.write(readBytes,0,readCount);
            }
            if(resEncoding!=null && resEncoding.length()>0) {
              return bos.toString(resEncoding);
            }
            return bos.toString();
        } catch (IOException e) {
            //e.printStackTrace(); 都抛出去了，没必要打出来
          throw new HttpException(sUrl,params,statusCode,statusMsg,e);
        }finally {
            try {
              is.close();
            } catch (Exception e2) {}
            try {
              bos.close();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 调用目标URL
     * @param  sUrl        URL地址
     * @param  header      自定义报文头容器
     * @param  req         请求对象
     * @param  resp        反馈对象
     * @param  os          输出流（如果为空，则使用resp中的输出流） 
     *                     注意：如果传入该值希望获取返回流做处理，
     *                          需要判断一下请求头中的Content-Encoding
     *                          是不是gzip，否则返回的内容都是压缩的
     * @param redirBaseurl    如果重定向，需要设置的根路径
     * @param sessionKeyName  在Cookie中，存放会话主键值的名
     * @param sessionKeyValue 上次调用该服务器时返回的会话信息值
     * @return 本次调用后返回的目标系统会话主键
     * @throws Exception   异常
     * 2019年9月10日
     * @author MBG
     */
  public static String[] call(
        String              sUrl,
        Map<String,String>  header,
        HttpServletRequest  req,
        HttpServletResponse resp,
        OutputStream        os,
        String              redirBaseurl,
        String              sessionKeyName,
        String              sessionKeyValue) throws Exception {
      byte[]       readBytes  = new byte[BUFFER_SIZE]; //读入缓存
      int          readCount;                          //读取的字节数
      int          statusCode = 0;                     //返回状态码
      String       statusMsg  = "";                    //返回信息
      InputStream  is         = null;                  //读入流
      String[]     res        = new String[3];         //构建返回值
      //尝试从线程容器中获取日志对象
      ILog log = (ILog)ThreadSession.get("log");
      if(log!=null) {
        log.log("Begin Call Url:["+sUrl+"]");
      }
      //构造URL
      URL url = new URL(sUrl);
      //构造链接
      URLConnection urlc = url.openConnection();
      //超时时间
      int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
      if(timeOut>0) {
        //设置链接超时时间
        urlc.setConnectTimeout(timeOut);
      }
      timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
      if(timeOut>0) {
        //设置读取超时时间
        urlc.setReadTimeout(timeOut);
      }
      if(urlc instanceof HttpsURLConnection) {
        //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
        ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
          @Override
                public boolean verify(String hostname, SSLSession session) {
            return true;
          }
        });
        ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
      }
      //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
      ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);

      /*
       * 设置请求报文头
       */
      //设置禁止缓存，否则可能会返回304，从缓存获取
      urlc.setRequestProperty("Cache-Control","no-cache");
      urlc.setRequestProperty("Pragma","no-cache");
      
      //设置头信息
      Enumeration<String> headerNames = req.getHeaderNames();
      String key;   //头主键
      String value; //头信息值
      while(headerNames.hasMoreElements()){
        key = headerNames.nextElement();
        if(key==null || (header!=null && header.containsKey(key))) {
          continue;
        }
        value = req.getHeader(key);
        if(value.length()<1) {
          continue;
        }
        //判断cookie是将大小写统一
        if("set-cookie".equalsIgnoreCase(key)
            || "cookie".equalsIgnoreCase(key)
            || "cookie".equalsIgnoreCase(key)) {
          key = "Cookie";
          if(sessionKeyValue!=null && sessionKeyValue.length()>0) {
            if(value.startsWith(sessionKeyName+"=")) {
              value = sessionKeyValue;
            }
          }
        }
        urlc.setRequestProperty(key,value);
      }
      if(header!=null) {
        //主键迭代
        Iterator<String> keys = header.keySet().iterator();
        if(keys!=null) {
          while(keys.hasNext()) {
            key = keys.next();
            if(key==null || key.length()<1) {
              continue;
            }
            value = header.get(key);
            if(value==null) {
              continue;
            }
            urlc.setRequestProperty(key,value);
          }
        }
      }
      //设置请求类型
      ((HttpURLConnection)urlc).setRequestMethod(req.getMethod());
      urlc.setDoOutput(true);   
      urlc.setUseCaches(false);
      if(SLong.valueOf(req.getHeader("Content-Length"))>0) {
        OutputStream dataOs = null; //提交数据流
        try {
          is     = req.getInputStream();
          dataOs = urlc.getOutputStream();
          while((readCount=is.read(readBytes))!=-1) {
            //放入缓存
            dataOs.write(readBytes,0,readCount);
          }
          dataOs.flush();
        }finally {
          try {
            is.close();
          }catch(Exception e2) {}
          try {
            dataOs.close();
          }catch(Exception e2) {}
        }
      }
      
      //设置返回值代码
      statusCode = ((HttpURLConnection)urlc).getResponseCode();
      statusMsg  = ((HttpURLConnection)urlc).getResponseMessage();
      res[0] = String.valueOf(statusCode);
      res[1] = statusMsg;
      
      /*
       * 设置返回报文头
       */
      String headerKey;          //头主键
      String headerValue;        //头值
      //没有结束上限，在循环体中退出，大于索引数时不会抛错
      for(int i=0;;i++) {
        headerKey   = SString.valueOf(urlc.getHeaderFieldKey(i));
        headerValue = SString.valueOf(urlc.getHeaderField(i));
          
        if (headerKey.length()<1 && headerValue.length()<1) {
          //如果主键和值都为空，头结束
          break;
        }
        if (headerKey.length()<1|| headerValue.length()<1) {
            //主键和值其中一个为空，忽略
          continue;
        }
        
        //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
        if(redirBaseurl!=null && redirBaseurl.length()>0 && 
          (statusCode==HttpURLConnection.HTTP_MOVED_TEMP 
            || statusCode==HttpURLConnection.HTTP_MOVED_PERM) 
                && "Location".equals(headerKey)) {
          int point = headerValue.indexOf("://");
              if(point>0) {
                headerValue = headerValue.substring(point+3);
                point = headerValue.indexOf("/");
                if(point>0) {
                  headerValue = headerValue.substring(point);
                }
              }
              headerValue = redirBaseurl+headerValue;
        }
          
        //头部值序列  
        /*
         * 服务器返回Cookie时，可能会返回多个，比如：
         * Set-Cookie: JSESSIONID=69FA0483FD80E8E4B4A85352B6FAB8BB; Path=/app/; HttpOnly
         * Set-Cookie: PXSESSIONID=PX-14295943421520-938; Path=/app
         * Set-Cookie: PXSERVERID=app_server3; Path=/app
         * 
         * 但是我们发送的Cookie格式并不是返回的这种格式，而是这种格式
         * Cookie: PXSESSIONID=PX-14295943421520-938; PXSERVERID=app_server1; JSESSIONID=PX1429753309470-14297533094700-29
         */
        if("set-cookie".equalsIgnoreCase(headerKey) && sessionKeyName!=null 
            && headerValue.startsWith(sessionKeyName+"=")) {
          sessionKeyValue = headerValue;
        }
        if(os!=null) {
          //需要对输出值做处理
          if("Transfer-Encoding".equals(headerKey) || "Content-Encoding".equals(headerKey)) {
            continue;
          }
        }
        //这里不能用setHeader，因为比如Set-Cookie，需要设置多个同主键名的值
        resp.addHeader(headerKey,headerValue);
      }
      
      res[2] = sessionKeyValue;
      if(statusCode!=200) {
        resp.sendError(statusCode,statusMsg);
        return res;
      }
      
    try {
      is = urlc.getInputStream();
      if(os==null) {
        os = resp.getOutputStream();
      }else {
        if("gzip".equals(urlc.getHeaderField("Content-Encoding"))) {
          is = new GZIPInputStream(is);
        }
      }
      while ((readCount = is.read(readBytes))!=-1) {
        os.write(readBytes,0,readCount);
      }
      os.flush();
    }finally {
      try {
        is.close();
      }catch(Exception e2) {}
      try {
        os.close();
      }catch(Exception e2) {}
    }
    
      if(log!=null) {
        log.log("Begin Call Url:["+sUrl+"] Over.......7");
      }
      
      
    return res;
    }
    
    /**
     * 调用目标URL并返回XML值
     * @param url          目标URL
     * @param params       提交数据
     * @param sendType     提交数据类型  0html格式  1xml格式  2json格式
     * @param headerMap    页面头
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static INodeHandler xCall(
        String url,String params,int sendType,Map<String,String> headerMap) throws Exception {
      return (INodeHandler)nativeCall(url,params,headerMap,sendType,1);
    }
    
    /**
     * 调用目标URL并返回XML值
     * @param url          目标URL
     * @param params       提交数据
     * @param headerMap    页面头
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static INodeHandler xCall(
        String url,Object params,Map<String,String> headerMap) throws Exception {
      return (INodeHandler)nativeCall(url,params,headerMap,1);
    }
    
    
    /**
     * 调用目标URL并返回XML值
     * @param url          目标URL
     * @param params       提交数据
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static INodeHandler xCall(String url,Object params) throws Exception {
      return (INodeHandler)nativeCall(url,params,null,1);
    }
    
    
    /**
     * 调用目标URL并返回XML值
     * @param url          目标URL
     * @param params       提交数据
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static INodeHandler xCall(String url) throws Exception {
      return (INodeHandler)nativeCall(url,null,null,1);
    }
    
    /**
     * 调用目标URL并返回Json值
     * @param url          目标URL
     * @param params       提交数据
     * @param sendType     提交数据类型  0html格式  1xml格式  2json格式
     * @param headerMap    页面头
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static Json jCall(
        String url,String params,int sendType,Map<String,String> headerMap) throws Exception {
      return (Json)nativeCall(url,params,headerMap,sendType,2);
    }
    
    /**
     * 调用目标URL并返回Json值
     * @param url          目标URL
     * @param params       提交数据
     * @param headerMap    页面头
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static Json jCall(
        String url,Object params,Map<String,String> headerMap) throws Exception {
      return (Json)nativeCall(url,params,headerMap,2);
    }
    
    /**
     * 调用目标URL并返回Json值
     * @param url          目标URL
     * @param params       提交数据
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static Json jCall(String url,Object params) throws Exception {
      return (Json)nativeCall(url,params,null,2);
    }
    
    /**
     * 调用目标URL并返回Json值
     * @param url          目标URL
     * @param params       提交数据
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static Json jCall(String url) throws Exception {
      return (Json)nativeCall(url,null,null,2);
    }
    
    /**
     * 调用目标URL并返回字符串值
     * @param url          目标URL
     * @param params       提交数据
     * @param sendType     提交数据类型  0html格式  1xml格式  2json格式
     * @param headerMap    页面头
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static String sCall(
        String url,String params,int sendType,Map<String,String> headerMap) throws Exception {
      return (String)nativeCall(url,params,headerMap,sendType,0);
    }
    
    /**
     * 调用目标URL并返回字符串值
     * @param url          目标URL
     * @param params       提交数据
     * @param headerMap    页面头
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static String sCall(
        String url,Object params,Map<String,String> headerMap) throws Exception {
      return (String)nativeCall(url,params,headerMap,0);
    }
    
    /**
     * 调用目标URL并返回字符串值
     * @param url          目标URL
     * @param params       提交数据
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static String sCall(String url,Object params) throws Exception {
      return (String)nativeCall(url,params,null,0);
    }
    
    /**
     * 调用目标URL并返回字符串值
     * @param url          目标URL
     * @param params       提交数据
     * @return             返回值
     * @throws Exception   异常
     * 2017年4月22日
     * @author MBG
     */
    public static String sCall(String url) throws Exception {
      return (String)nativeCall(url,null,null,0);
    }
    
    /**
     * 调用目标并返回指定类型值
     * @param sUrl          目标URL
     * @param params        提交数据   可以为 String 即 aaa=111&bbb=222  可以为XML 即 IViewHandler  可以为  Json
     * @param headerMap     头信息容器
     * @param resType       返回值类型  0String 1XML  2Json
     * @return              返回值对象
     * @throws Exception    异常
     * 2017年4月22日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    private static Object nativeCall(
        String sUrl,Object params,Map<String,String> headerMap,int resType) throws Exception {
        if(params==null) {
          return nativeCall(sUrl,null,headerMap,0,resType);
        }else if(params instanceof IViewHandler) {
          return nativeCall(sUrl,((IViewHandler)params).getNodeBody(),headerMap,1,resType);
        }else if(params instanceof Json) {
          return nativeCall(sUrl, params.toString(),headerMap,2,resType);
        }else if(params instanceof String) {
          return nativeCall(sUrl,(String)params,headerMap,0,resType);
        }else if(params instanceof Map) {
          //将传入的Map转换为Post字符串
          return nativeCall(sUrl,StringUtil.getParaStringFromMap((Map)params),headerMap,0,resType);
        }
        return nativeCall(sUrl,params.toString(),headerMap,0,resType);
    }
    
    /**
     * 调用目标并返回指定类型值
     * @param sUrl          目标URL
     * @param params        提交数据
     * @param headerMap     头信息容器
     * @param sendType      发送内容类型 0html  1xml  2json
     * @param resType       返回值类型  0String 1XML  2Json
     * @return              返回值对象
     * @throws Exception    异常
     * 2017年4月22日
     * @author MBG
     */
    private static Object nativeCall(
        String sUrl,String params,Map<String,String> headerMap,int sendType,int resType) throws Exception {
      if(headerMap==null) {
        headerMap = new HashMap<String,String>();
      }
        ByteArrayOutputStream bos = null; //字节输出流
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin xCall Url:["+sUrl+"] params:["+params+"]");
          }
          //发送内容编码
          String sendEncoding = SString.valueOf(ThreadSession.get("_httpcall_send_encoding"));
          if(sendEncoding.length()<1) {
            sendEncoding = "UTF-8";
          }
          //接收内容编码
          String resEncoding = SString.valueOf(ThreadSession.get("_httpcall_res_encoding"));
          if(resEncoding.length()<1) {
            resEncoding = "UTF-8";
          }
          //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            byte[] sendBytes;    //发送内容字节数组
            if(params==null) {
              sendBytes = new byte[0];
            }else {
              sendBytes = params.getBytes(sendEncoding);
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,sendType,sendEncoding); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (sendBytes.length>0) {
              //构建输出信息流
              DataOutputStream out = new DataOutputStream(urlc.getOutputStream());   
              out.write(sendBytes);
              out.flush();
              out.close();   
            }
            urlc.connect();
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向URL
              String goUrl = SString.valueOf(headerMap.get("Location"));
              if(goUrl.indexOf("://")<0) {
                goUrl = getBaseUrl(sUrl)+goUrl;
              }
              if(log!=null) {
                log.log("xCall SendRedirectURL:["+goUrl+"]");
              }
              return nativeCall(goUrl,params,headerMap,resType);
            }
            //获取信息流
            is = urlc.getInputStream();
            
            if(resType==1) {
              /* 返回XML对象 */
              //构建返回值
              INodeHandler nh = FNodeHandler.newNodeHandler();
              nh.setDealEncode(resEncoding);
              if(log!=null) {
                    //建立字节输出流
                    bos = new ByteArrayOutputStream();
                    int readCount; //读取字节
                    while ((readCount = is.read(readBytes))!=-1) {
                      bos.write(readBytes,0,readCount);
                    }
                    //返回信息内容
                    String restr = bos.toString(resEncoding);
                    log.log("xCall  Url:["+sUrl+"]  ResXmlContent:\n"+restr);
                    nh.setNodeBody(restr);
              }else {
                nh.setStream(is,"From Url:["+sUrl+"]");
              }
              return nh;
            }else {
                //建立字节输出流
                bos = new ByteArrayOutputStream();
                int readCount; //读取字节
                while ((readCount = is.read(readBytes))!=-1) {
                  bos.write(readBytes,0,readCount);
                }
                if(log!=null) {
                  log.log("xCall  Url:["+sUrl+"]  "+(resType==2?"Json":"Html")+"Content:\n"+bos.toString(resEncoding));
                }
                if(resType==2) {
                  /* 返回Json对象 */
                  return new Json(bos.toString(resEncoding));
                }
                return bos.toString(resEncoding);
            }
        } catch (IOException e) {
            //e.printStackTrace(); 都抛出去了，没必要打出来
          throw new HttpException(sUrl,params,statusCode,statusMsg,e);
        }finally {
            try {
              is.close();
            } catch (Exception e2) {}
            try {
              bos.close();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 以DELETE方式提交数据
     * @param sUrl         目标服务器URL
     * @param data         提交数据：String Json IViewHandler
     * @return             返回信息字符串
     * @throws Exception   异常
     * 2018年8月20日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    public static String delete(String sUrl,Object data) throws Exception {
      int    contentType;          //提交数据类型
      String dataStr       = null; //提交内容
      if(data instanceof Json) {
        contentType = 2;
        dataStr = data.toString();
      }else if(data instanceof IViewHandler) {
        contentType = 1;
        dataStr = ((IViewHandler)data).getNodeBody();
      }else if(data instanceof Map) {
        contentType = 0;
          //将传入的Map转换为Post字符串
        dataStr = StringUtil.getParaStringFromMap((Map)data);
      }else {
        contentType = 0;
        if(data!=null) {
          dataStr = data.toString();
        }
      }
      return call("DELETE",sUrl,dataStr,contentType,null,"UTF-8","UTF-8");
    }
    
    /**
     * 以PUT方式提交数据
     * @param sUrl         目标服务器URL
     * @param data         提交数据：String Json IViewHandler
     * @return             返回信息字符串
     * @throws Exception   异常
     * 2018年8月20日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    public static String put(String sUrl,Object data) throws Exception {
      int    contentType;          //提交数据类型
      String dataStr       = null; //提交内容
      if(data instanceof Json) {
        contentType = 2;
        dataStr = data.toString();
      }else if(data instanceof IViewHandler) {
        contentType = 1;
        dataStr = ((IViewHandler)data).getNodeBody();
      }else if(data instanceof Map) {
        contentType = 0;
          //将传入的Map转换为Post字符串
        dataStr = StringUtil.getParaStringFromMap((Map)data);
      }else {
        contentType = 0;
        if(data!=null) {
          dataStr = data.toString();
        }
      }
      return call("PUT",sUrl,dataStr,contentType,null,"UTF-8","UTF-8");
    }
    
    /**
     * 以PATCH方式提交数据
     * @param sUrl         目标服务器URL
     * @param data         提交数据：String Json IViewHandler
     * @return             返回信息字符串
     * @throws Exception   异常
     * 2018年8月20日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    public static String patch(String sUrl,Object data) throws Exception {
      int    contentType;          //提交数据类型
      String dataStr       = null; //提交内容
      if(data instanceof Json) {
        contentType = 2;
        dataStr = data.toString();
      }else if(data instanceof IViewHandler) {
        contentType = 1;
        dataStr = ((IViewHandler)data).getNodeBody();
      }else if(data instanceof Map) {
        contentType = 0;
          //将传入的Map转换为Post字符串
        dataStr = StringUtil.getParaStringFromMap((Map)data);
      }else {
        contentType = 0;
        if(data!=null) {
          dataStr = data.toString();
        }
      }
      return call("PATCH",sUrl,dataStr,contentType,null,"UTF-8","UTF-8");
    }
    
    /**
     * 以TRACE方式提交数据
     * @param sUrl         目标服务器URL
     * @param data         提交数据：String Json IViewHandler
     * @return             返回信息字符串
     * @throws Exception   异常
     * 2018年8月20日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    public static String trace(String sUrl,Object data) throws Exception {
      int    contentType;          //提交数据类型
      String dataStr       = null; //提交内容
      if(data instanceof Json) {
        contentType = 2;
        dataStr = data.toString();
      }else if(data instanceof IViewHandler) {
        contentType = 1;
        dataStr = ((IViewHandler)data).getNodeBody();
      }else if(data instanceof Map) {
        contentType = 0;
          //将传入的Map转换为Post字符串
        dataStr = StringUtil.getParaStringFromMap((Map)data);
      }else {
        contentType = 0;
        if(data!=null) {
          dataStr = data.toString();
        }
      }
      return call("TRACE",sUrl,dataStr,contentType,null,"UTF-8","UTF-8");
    }
    
    /**
     * 以其它方式提交数据
     * @param method       提交方式：GET,POST,HEAD,PUT,DELETE,PATCH
     * @param sUrl         目标服务器URL
     * @param data         提交数据：String Json IViewHandler
     * @return             返回信息字符串
     * @throws Exception   异常
     * 2018年8月20日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    public static String call(String method,String sUrl,Object data) throws Exception {
      int    contentType;          //提交数据类型
      String dataStr       = null; //提交内容
      if(data instanceof Json) {
        contentType = 2;
        dataStr = data.toString();
      }else if(data instanceof IViewHandler) {
        contentType = 1;
        dataStr = ((IViewHandler)data).getNodeBody();
      }else if(data instanceof Map) {
        contentType = 0;
          //将传入的Map转换为Post字符串
        dataStr = StringUtil.getParaStringFromMap((Map)data);
      }else {
        contentType = 0;
        if(data!=null) {
          dataStr = data.toString();
        }
      }
      return call(method,sUrl,dataStr,contentType,null,"UTF-8","UTF-8");
    }
    
    /**
     * HTTP方式调用
     * @param url          目标URL
     * @param data         Post数据：null, String(key=value&key=value) , Json  IViewHandler(XML)
     * @param contentType  提交数据类型     0 key=value&key=value 1XML  2Json 注意：即使data为字符串格式，也可能是XML或者Json
     * @param header       报文头容器
     * @return             目标数据读入流
     * @throws Exception   异常
     * 2019年9月4日
     * @author MBG
     */
    public static InputStream call(
        String             urlStr,
        Object             data,
        int                contentType,
        Map<String,String> header,
        String             sendEnc) throws Exception {
      if(urlStr==null || urlStr.length()<1) {
        throw new Exception("HttpCall.call Url Is Empty");
      }
        String method;      //提交方式 POST  GEt
        byte[] postData;    //提交数据
        if(data==null) {
          method      = "GET";
          postData    = null;
        }else {
          method      = "POST";
          if(sendEnc==null || sendEnc.length()<1) {
            sendEnc = "UTF-8";
          }
          if(data instanceof String) {
            postData    = ((String)data).getBytes(sendEnc);
          }else if(data instanceof Json) {
            postData    = data.toString().getBytes(sendEnc);
          }else if(data instanceof IViewHandler) {
            postData    = ((IViewHandler)data).getNodeBody().getBytes(sendEnc);
          }else {
            postData    = data.toString().getBytes(sendEnc);
          }
        }
        //尝试从线程容器中获取日志对象
        ILog log = (ILog)ThreadSession.get("log");
        if(log!=null) {
          log.log("Begin Call Url:["+urlStr+"]");
        }
        //构造URL
        URL url = new URL(urlStr);
        //构造链接
        URLConnection urlc = url.openConnection();
        //超时时间
        int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
        if(timeOut>0) {
          //设置链接超时时间
          urlc.setConnectTimeout(timeOut);
        }
        timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
        if(timeOut>0) {
          //设置读取超时时间
          urlc.setReadTimeout(timeOut);
        }
        if(urlc instanceof HttpsURLConnection) {
          //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
          ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
            @Override
                public boolean verify(String hostname, SSLSession session) {
              return true;
            }
          });
          ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
        }
        //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
        ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
        ((HttpURLConnection)urlc).setRequestMethod(method);  //设置请求类型
        setHeaderInfo(urlc,header,null,contentType,null); //设置头信息
        urlc.setDoInput(true);
        urlc.setDoOutput(true);   
        urlc.setUseCaches(false);
        if (postData!=null && postData.length>0) {
          //设置提交数据长度
          urlc.setRequestProperty("Content-Length",String.valueOf(postData.length));
          //构建提交数据输出流
          OutputStream os = new DataOutputStream(urlc.getOutputStream());
          try {
            os.write(postData);
          }catch(Exception e) {
            e.printStackTrace();
          }finally {
          
          }
        }
        int statusCode; //请求返回值
        try {
          statusCode = ((HttpURLConnection)urlc).getResponseCode();
        }catch(Exception e) {
          throw new HttpException(urlStr,500,e.toString());
        }
        String statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
        fixHeadInfo(urlc,header); //处理 头信息
        //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
        if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
          //重定向URL
          String goUrl = SString.valueOf(header.get("Location"));
          if(goUrl.indexOf("://")<0) {
            goUrl = getBaseUrl(urlStr)+goUrl;
          }
          if(log!=null) {
            log.log("HttpCall SendRedirectURL:["+goUrl+"]");
          }
          return call(goUrl,data,contentType,header,sendEnc);
        }else if(statusCode!=HttpURLConnection.HTTP_OK) {
          throw new HttpException(urlStr,statusCode,statusMsg);
        }
        //获取信息流
        return urlc.getInputStream();
    }
    
    
    /**
     * HTTP方式调用
     * @param url          目标URL
     * @param data         Post数据：null, String(key=value&key=value) , Json  IViewHandler(XML)
     * @param contentType  提交数据类型     0 key=value&key=value 1XML  2Json 注意：即使data为字符串格式，也可能是XML或者Json
     * @param headerKeys   报文头主键数组
     * @param headerVals   报文头值数组
     * @param respHeader   返回报文头
     * @return             目标数据读入流
     * @throws Exception   异常
     * 2019年9月4日
     * @author MBG
     */
    public static InputStream call(
        String             urlStr,
        Object             data,
        int                contentType,
        String[]           headerKeys,
        String[]           headerVals,
        Map<String,String> respHeader,
        String             sendEnc) throws Exception {
      if(urlStr==null || urlStr.length()<1) {
        throw new Exception("HttpCall.call Url Is Empty");
      }
        String method;      //提交方式 POST  GEt
        byte[] postData;    //提交数据
        if(data==null) {
          method      = "GET";
          postData    = null;
        }else {
          method      = "POST";
          if(sendEnc==null || sendEnc.length()<1) {
            sendEnc = "UTF-8";
          }
          if(data instanceof String) {
            postData    = ((String)data).getBytes(sendEnc);
          }else if(data instanceof Json) {
            postData    = data.toString().getBytes(sendEnc);
          }else if(data instanceof IViewHandler) {
            postData    = ((IViewHandler)data).getNodeBody().getBytes(sendEnc);
          }else {
            postData    = data.toString().getBytes(sendEnc);
          }
        }
        //尝试从线程容器中获取日志对象
        ILog log = (ILog)ThreadSession.get("log");
        if(log!=null) {
          log.log("Begin Call Url:["+urlStr+"]");
        }
        //构造URL
        URL url = new URL(urlStr);
        //构造链接
        URLConnection urlc = url.openConnection();
        //超时时间
        int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
        if(timeOut>0) {
          //设置链接超时时间
          urlc.setConnectTimeout(timeOut);
        }
        timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
        if(timeOut>0) {
          //设置读取超时时间
          urlc.setReadTimeout(timeOut);
        }
        if(urlc instanceof HttpsURLConnection) {
          //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
          ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
            @Override
                public boolean verify(String hostname, SSLSession session) {
              return true;
            }
          });
          ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
        }
        //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
        ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
        ((HttpURLConnection)urlc).setRequestMethod(method);  //设置请求类型
        setHeaderInfo(urlc,headerKeys,headerVals,null,contentType,null); //设置头信息
        urlc.setDoInput(true);
        urlc.setDoOutput(true);   
        urlc.setUseCaches(false);
        if (postData!=null && postData.length>0) {
          //设置提交数据长度
          urlc.setRequestProperty("Content-Length",String.valueOf(postData.length));
          //构建提交数据输出流
          OutputStream os = new DataOutputStream(urlc.getOutputStream());
          try {
            os.write(postData);
          }catch(Exception e) {
            e.printStackTrace();
          }finally {
            
          }
        }
        int    statusCode = ((HttpURLConnection)urlc).getResponseCode();
        String statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
        if(respHeader==null) {
          respHeader = new HashMap<String,String>();
        }
        fixHeadInfo(urlc,respHeader); //处理 头信息
        //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
        if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
          //重定向URL
          String goUrl = SString.valueOf(respHeader.get("Location"));
          if(goUrl.indexOf("://")<0) {
            goUrl = getBaseUrl(urlStr)+goUrl;
          }
          if(log!=null) {
            log.log("HttpCall SendRedirectURL:["+goUrl+"]");
          }
          return call(goUrl,data,contentType,headerKeys,headerVals,respHeader,sendEnc);
        }else if(statusCode!=HttpURLConnection.HTTP_OK) {
          throw new HttpException(urlStr,statusCode,statusMsg);
        }
        //获取信息流
        return urlc.getInputStream();
    }
    
    /**
     * 高级调用目标接口(可以走代理）
     * 
     * 如果代理需要账号密码，需要在headerMap中增加以下值
     * 
     * headerMap.put("Proxy-Authorization","Basic "+Base64.base64Encode("account:password","UTF-8"));
     * 
     * @param sUrl         目标URL
     * @param data         提交数据字符串
     * @param contentType  提交类型 0 html 1xml 2json
     * @param headerMap    请求头对象容器
     * @param sendEnc      提交数据编码格式，默认UTF-8
     * @param resEnc       返回数据编码格式，默认UTF-8
     * @param method       提交类型
     * @param proxyHost    如果需要走代理，代理服务器ip地址
     * @param proxyPort    代理服务器端口
     * @return             请求返回值
     * @throws Exception   异常
     * 2020年3月2日
     * @author MBG
     */
    public static String advCall(
        String             sUrl,
        String             data,
        int                contentType,
        Map<String,String> headerMap,
        String             sendEnc,
        String             resEnc,
        String             method,
        String             proxyHost,
        String             proxyPort) throws Exception {
        ByteArrayOutputStream bos        = null;                  //字节输出流
        InputStream           is         = null;                  //读入信息流
        byte[]                readBytes  = new byte[BUFFER_SIZE]; //读入缓存
        int                   statusCode = 0;                     //返回状态码
        String                statusMsg  = "";                    //返回状态信息
        if(method==null || method.length()<1) {
          if(data==null || data.length()<1) {
            method = "GET";
          }else {
            method = "POST";
          }
        }else {
          method = method.toUpperCase();
        }
        if(headerMap==null) {
          headerMap = new HashMap<String,String>();
        }
    try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[InputStream] contentType:["+contentType+"]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = null;
            if(proxyHost!=null && proxyHost.length()>0 && proxyPort!=null && proxyPort.length()>0) {
              //构建代理地址信息
              InetSocketAddress addr = new InetSocketAddress(proxyHost,SInteger.valueOf(proxyPort));  
              Proxy proxy = new Proxy(Proxy.Type.HTTP, addr); // http 代理  
              urlc = url.openConnection(proxy);
            }else {
              urlc = url.openConnection();
            }
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            ((HttpURLConnection)urlc).setRequestMethod(method);  //设置请求类型
            setHeaderInfo(urlc,headerMap,null,contentType,null); //设置头信息
            urlc.setDoInput(true);
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (data!=null && data.length()>0) {
              //构建输出信息流
              DataOutputStream out = 
                new DataOutputStream(urlc.getOutputStream());   
              if(sendEnc!=null && sendEnc.length()>0) {
                out.write(data.getBytes(sendEnc));
              }else {
                out.writeBytes(data);
              }
              out.flush();
              out.close();   
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向URL
              String goUrl = SString.valueOf(headerMap.get("Location"));
              if(goUrl.indexOf("://")<0) {
                goUrl = getBaseUrl(sUrl)+goUrl;
              }
              if(log!=null) {
                log.log("HttpCall SendRedirectURL:["+goUrl+"]");
              }
              return call(method,goUrl,data,contentType,headerMap,sendEnc,resEnc);
            }
            //获取信息流
            is = urlc.getInputStream();
            //建立字节输出流
            bos = new ByteArrayOutputStream();
            
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
              bos.write(readBytes,0,readCount);
            }
            if(resEnc!=null && resEnc.length()>0) {
              return bos.toString(resEnc);
            }
            return bos.toString();
        } catch (IOException e) {
            //e.printStackTrace(); 都抛出去了，没必要打出来
          throw new HttpException(sUrl,data,statusCode,statusMsg,e);
        }finally {
            try {
              is.close();
            } catch (Exception e2) {}
            try {
              bos.close();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 以HTTP协议访问目标服务器
     * @param method         访问方式：GET,POST,HEAD,PUT,DELETE,PATCH
     * @param sUrl           目标URL
     * @param data           提交数据内容
     * @param contentType    数据类型 : 0 html 1xml 2json
     * @param headerMap      报文头容器
     * @param sendEnc        发送内容编码
     * @param resEnc         接收内容编码
     * @return               返回内容
     * @throws Exception     异常
     * 2018年8月20日
     * @author MBG
     */
    public static String call(
        String             method,
        String             sUrl,
        String             data,
        int                contentType,
        Map<String,String> headerMap,
        String             sendEnc,
        String             resEnc) throws Exception {
        ByteArrayOutputStream bos        = null;                  //字节输出流
        InputStream           is         = null;                  //读入信息流
        byte[]                readBytes  = new byte[BUFFER_SIZE]; //读入缓存
        int                   statusCode = 0;                     //返回状态码
        String                statusMsg  = "";                    //返回状态信息
        if(method==null || method.length()<1) {
          if(data==null || data.length()<1) {
            method = "GET";
          }else {
            method = "POST";
          }
        }else {
          method = method.toUpperCase();
        }
    try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[InputStream] contentType:["+contentType+"]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            ((HttpURLConnection)urlc).setRequestMethod(method);  //设置请求类型
            setHeaderInfo(urlc,headerMap,null,contentType,null); //设置头信息
            urlc.setDoInput(true);
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (data!=null && data.length()>0) {
              //构建输出信息流
              DataOutputStream out = 
                new DataOutputStream(urlc.getOutputStream());   
              if(sendEnc!=null && sendEnc.length()>0) {
                out.write(data.getBytes(sendEnc));
              }else {
                out.writeBytes(data);
              }
              out.flush();
              out.close();   
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向URL
              String goUrl = SString.valueOf(headerMap.get("Location"));
              if(goUrl.indexOf("://")<0) {
                goUrl = getBaseUrl(sUrl)+goUrl;
              }
              if(log!=null) {
                log.log("HttpCall SendRedirectURL:["+goUrl+"]");
              }
              return call(method,goUrl,data,contentType,headerMap,sendEnc,resEnc);
            }
            //获取信息流
            is = urlc.getInputStream();
            //建立字节输出流
            bos = new ByteArrayOutputStream();
            
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
              bos.write(readBytes,0,readCount);
            }
            if(resEnc!=null && resEnc.length()>0) {
              return bos.toString(resEnc);
            }
            return bos.toString();
        } catch (IOException e) {
            //e.printStackTrace(); 都抛出去了，没必要打出来
          throw new HttpException(sUrl,data,statusCode,statusMsg,e);
        }finally {
            try {
              is.close();
            } catch (Exception e2) {}
            try {
              bos.close();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 调用URL并获取返回值
     * @author 马宝刚
     * @param sUrl        访问路径
     * @param is          请求数据读入流
     * @param contentType 内容类型
     * @param headerMap   头信息容器(可以同步会话，获取头信息)
     * @throws Exception  执行发生异常
     * 2009-3-25  下午02:25:34
     */
    public static void call(
            String             sUrl,
            InputStream        is,
            String             contentType,
            Map<String,String> headerMap) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[InputStream] contentType:["+contentType+"]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,contentType,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (is!=null) {
                //构建输出信息流
              OutputStream os = urlc.getOutputStream();
                int rCount; //读取的字节数
                byte[] buffer = new byte[BUFFER_SIZE]; //读入缓存
                while((rCount=is.read(buffer))!=-1) {
                    //放入缓存
                  os.write(buffer,0,rCount);
                }
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
        } catch (IOException e) {
          throw new HttpException(sUrl,"[InputStream Data]",statusCode,statusMsg,e);
        }finally {
            try {
                is.close();
            } catch (Exception e2) {}
        }
    }
    
    /**
     * 调用URL并获取返回值
     * @param sUrl       目标URL
     * @param datas      POST数据字节数组
     * @param headerMap  报文头对象
     * @return           接收数据流
     * @throws Exception 异常
     * 2014年9月16日
     * @author 马宝刚
     */
    public static InputStream call(
            String             sUrl,
            byte[]             datas,
            Map<String,String> headerMap) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[datas]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if(datas!=null && datas.length>0) {
                //构建输出信息流
                DataOutputStream out =  new DataOutputStream(urlc.getOutputStream()); 
                out.write(datas);
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向URL
              String goUrl = SString.valueOf(headerMap.get("Location"));
              if(goUrl.indexOf("://")<0) {
                goUrl = getBaseUrl(sUrl)+goUrl;
              }
              if(log!=null) {
                log.log("HttpCall SendRedirectURL:["+goUrl+"]");
              }
                return call(goUrl,(byte[])null,headerMap);
            }
            //获取信息流
            return urlc.getInputStream();
        } catch (IOException e) {
          throw new HttpException(sUrl,new String(datas),statusCode,statusMsg,e);
        }
    }
    
    /**
     * 调用URL并获取返回值
     * @author 马宝刚
     * @param sUrl        访问路径
     * @param datas       提交数据字节数组
     * @param contentType 内容类型
     * @param headerMap   头信息容器(可以同步会话，获取头信息)
     * @throws Exception  执行发生异常
     * 2009-3-25  下午02:25:34
     */
    public static void call(
            String             sUrl,
            byte[]             datas,
            String             contentType,
            Map<String,String> headerMap) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[datas]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if(datas!=null && datas.length>0) {
                //构建输出信息流
                DataOutputStream out =  new DataOutputStream(urlc.getOutputStream()); 
                out.write(datas);
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
        } catch (IOException e) {
          throw new HttpException(sUrl,new String(datas),statusCode,statusMsg,e);
        }
    }
    
    /**
     * 调用URL并获取返回值
     * @author 马宝刚
     * @param sUrl       访问路径
     * @param datas      提交数据字节数组
     * @param headerMap  头信息容器(可以同步会话，获取头信息)
     * @throws Exception 执行发生异常
     * 2009-3-25  下午02:25:34
     */
    public static byte[] callReturnBytes(
            String             sUrl,
            byte[]             datas,
            Map<String,String> headerMap) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        ByteArrayOutputStream bos = null; //字节输出流
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin CallReturnBytes Url:["+sUrl+"] params:[datas]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if(datas!=null && datas.length>0) {
                //构建输出信息流
                DataOutputStream out =  new DataOutputStream(urlc.getOutputStream()); 
                out.write(datas);
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
            //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
            if(statusCode==HttpURLConnection.HTTP_MOVED_TEMP || statusCode==HttpURLConnection.HTTP_MOVED_PERM) {
              //重定向URL
              String goUrl = SString.valueOf(headerMap.get("Location"));
              if(goUrl.indexOf("://")<0) {
                goUrl = getBaseUrl(sUrl)+goUrl;
              }
              if(log!=null) {
                log.log("HttpCall SendRedirectURL:["+goUrl+"]");
              }
                return callReturnBytes(goUrl,null,headerMap);
            }
            //获取信息流
            is = urlc.getInputStream();
            //建立字节输出流
            bos = new ByteArrayOutputStream();
            
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
                bos.write(readBytes,0,readCount);
            }
            return bos.toByteArray();
        } catch (IOException e) {
          throw new HttpException(sUrl,new String(datas),statusCode,statusMsg,e);
        }finally {
            try {
                is.close();
            } catch (Exception e2) {}
            try {
                bos.close();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 获取根URL
     * @param url 目标URL
     * @return    根URL
     * 2014年8月23日
     * @author 马宝刚
     */
    protected static String getBaseUrl(String url) {
      //构建返回值
      StringBuffer reSbf = new StringBuffer();
      int point = url.indexOf("//"); //分割点
      if(point<0) {
        return url;
      }
      reSbf.append(url, 0, point+2);
      url = url.substring(point+2);
      
      point = url.indexOf("/");
      if(point<0) {
        reSbf.append(url);
      }else {
        reSbf.append(url, 0, point);
      }
      return reSbf.toString();
    }
    
    /**
     * 设置头信息
     * @param conn             URL连接对象
     * @param headerKeys       报文头主键数组
     * @param headerVals       报文头值数组
     * @param contentTypeStr   已经定义好的内容类型
     * @param contentType      发送内容类型  0html  1xml  2json
     * @param sendEncoding     发送内容编码
     * @throws Exception       异常
     * 2015年4月22日
     * @author 马宝刚
     */
    private static void setHeaderInfo(
            URLConnection urlc,
            String[]      headerKeys,
            String[]      headerVals,
            String        contentTypeStr,
            int           contentType,
            String        sendEncoding) throws Exception {
      if(headerKeys==null 
          || headerKeys.length<1 
          || headerVals==null 
          || headerVals.length<headerKeys.length) {
        return;
      }
        for(int i=0;i<headerKeys.length;i++) {
          if(headerKeys[i]==null) {
            continue;
          }
            if("user-agent".equalsIgnoreCase(headerKeys[i])
                || "Content-Type".equalsIgnoreCase(headerKeys[i])) {
                continue;
            }
            if(headerVals[i]==null || headerVals[i].length()<1) {
                continue;
            }
            //判断cookie是将大小写统一
            if("set-cookie".equalsIgnoreCase(headerKeys[i]) || "cookie".equalsIgnoreCase(headerKeys[i])) {
              headerKeys[i] = "Cookie";
            }
            urlc.setRequestProperty(headerKeys[i],headerVals[i]);
        }
        urlc.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36");
        if(contentTypeStr!=null && contentTypeStr.length()>0) {
            urlc.setRequestProperty("Content-Type",contentTypeStr);
        }else {
            if (contentType==1) {
                //xml
                if(sendEncoding==null || sendEncoding.length()<1) {
                    urlc.setRequestProperty("Content-Type","text/xml; charset=UTF-8");
                }else {
                    urlc.setRequestProperty("Content-Type","text/xml; charset="+sendEncoding);
                }
            }else if(contentType==2) {
                //json
                if(sendEncoding==null || sendEncoding.length()<1) {
                    urlc.setRequestProperty("Content-Type","application/json; charset=UTF-8");
                }else {
                    urlc.setRequestProperty("Content-Type","application/json; charset="+sendEncoding);
                }
            }else {
                //text
                urlc.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
            }
        }
    }
    
    /**
     * 设置头信息
     * @param conn             URL连接对象
     * @param headerMap        头信息容器
     * @param contentTypeStr   已经定义好的内容类型
     * @param contentType      发送内容类型  0html  1xml  2json
     * @param sendEncoding     发送内容编码
     * @throws Exception       异常
     * 2015年4月22日
     * @author 马宝刚
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static void setHeaderInfo(
            URLConnection urlc,
            Map           headerMap,
            String        contentTypeStr,
            int           contentType,
            String        sendEncoding) throws Exception {
        //设置头信息
        String[] keyList = BaseUtil.getMapKeys(headerMap);
        String value; //头信息值
        for(String key:keyList) {
            if("user-agent".equalsIgnoreCase(key) || "Content-Type".equalsIgnoreCase(key)) {
                continue;
            }
            value = SString.valueOf(headerMap.get(key));
            if(value.length()<1) {
                continue;
            }
            //判断cookie是将大小写统一
            if("set-cookie".equalsIgnoreCase(key) || "cookie".equalsIgnoreCase(key)) {
                key = "Cookie";
            }
            urlc.setRequestProperty(key,value);
        }
        urlc.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36");
        if(contentTypeStr!=null && contentTypeStr.length()>0) {
            urlc.setRequestProperty("Content-Type",contentTypeStr);
        }else {
          if(contentType==0) {
            //text
            urlc.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
          }else if (contentType==1) {
                //xml
                if(sendEncoding==null || sendEncoding.length()<1) {
                    urlc.setRequestProperty("Content-Type","text/xml; charset=UTF-8");
                }else {
                    urlc.setRequestProperty("Content-Type","text/xml; charset="+sendEncoding);
                }
            }else if(contentType==2) {
                //json
                if(sendEncoding==null || sendEncoding.length()<1) {
                    urlc.setRequestProperty("Content-Type","application/json; charset=UTF-8");
                }else {
                    urlc.setRequestProperty("Content-Type","application/json; charset="+sendEncoding);
                }
            }else if(contentType==3) {
              urlc.setRequestProperty("Content-Type","application/json; charset=UTF-8");
            }else {
                //file text
                urlc.setRequestProperty("Content-Type","multipart/form-data");
            }
        }
    }
    
    /**
     * 在报文头中设置需要登录的账号和密码（通常用于如果不设置账号密码就回返回401错误）
     * @param headerMap 请求头信息容器
     * @param uName     登录账号
     * @param pwd       登录密码
     */
    public static void setAuthorization(Map<String,String> headerMap,String uName,String pwd){
      if(headerMap==null || uName==null || uName.length()<1){return;}
      if(pwd==null){pwd="";}
      headerMap.put("Authorization", "Basic " + DatatypeConverter.printBase64Binary((uName+":"+pwd).getBytes()));
    }

    /**
     * 将头信息放入容器
     * @author 马宝刚
     * @param urlc      连接对象
     * @param headerMap 头信息容器
     * 2009-4-1 下午08:21:39
     */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static void fixHeadInfo(URLConnection urlc,Map headerMap) {
      if (headerMap==null) {
        return;
      }
      String headerKey;   //头主键
      String headerValue; //头值
      String value;       //参数值
      int point;          //字符串分割点
      //没有结束上限，在循环体中退出，大于索引数时不会抛错
      for(int i=0;;i++) {
        headerKey   = SString.valueOf(urlc.getHeaderFieldKey(i));
        headerValue = SString.valueOf(urlc.getHeaderField(i));
        if (headerKey.length()<1 && headerValue.length()<1) {
          //如果主键和值都为空，头结束
          return;
        }
        if (headerKey.length()<1|| headerValue.length()<1) {
            //主键和值其中一个为空，忽略
          continue;
        }
        //头部值序列  
        /*
         * 服务器返回Cookie时，可能会返回多个，比如：
         * Set-Cookie: JSESSIONID=69FA0483FD80E8E4B4A85352B6FAB8BB; Path=/app/; HttpOnly
         * Set-Cookie: PXSESSIONID=PX-14295943421520-938; Path=/app
         * Set-Cookie: PXSERVERID=app_server3; Path=/app
         * 
         * 但是我们发送的Cookie格式并不是返回的这种格式，而是这种格式
         * Cookie: PXSESSIONID=PX-14295943421520-938; PXSERVERID=app_server1; JSESSIONID=PX1429753309470-14297533094700-29
         */
        if("set-cookie".equalsIgnoreCase(headerKey)) {
            value = SString.valueOf(headerMap.get(headerKey));
            if(value.length()>0) {
                value += "; ";
            }
            point = headerValue.indexOf(";");
            if(point>-1) {
                headerValue = headerValue.substring(0,point);
            }
            value+=headerValue;
            headerMap.put(headerKey,value);
        }else {
            headerMap.put(headerKey,headerValue);
        }
      }
    }

  /**
   * 快速访问（通常用于检测网络）
   * @param sUrl       调用URL
   * @param timeOut    超时时间（毫秒）
   * @return           返回调用结果
   * @throws Exception 异常
   * 2015年11月9日
   * @author 马宝刚
   */
    public static String fastCall(
            String sUrl
            ,int timeOut) throws Exception {
        ByteArrayOutputStream bos = null; //字节输出流
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin fastCall Url:["+sUrl+"] timeOut:["+timeOut+"]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            urlc.setReadTimeout(timeOut);
            urlc.setConnectTimeout(timeOut);
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            //获取信息流
            is = urlc.getInputStream();
            //建立字节输出流
            bos = new ByteArrayOutputStream();
            
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
                bos.write(readBytes,0,readCount);
            }
            return bos.toString();
        }finally {
            try {
                is.close();
            } catch (Exception e2) {}
            try {
                bos.close();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 调用URL并直接输出
     * @param sUrl        调用url
     * @param dataMap     提交的参数
     * @param headerMap   报文头
     * @param os          输出流
     * @throws Exception  异常
     * 2015年11月16日
     * @author 马宝刚
     */
    public static void call(
            String             sUrl,
            Map<String,?>      dataMap,
            Map<String,String> headerMap,
            OutputStream       os) throws Exception {
        call(sUrl,dataMap,headerMap,os,0);
    }
    
    /**
     * 调用URL并直接输出
     * @param sUrl       调用url
     * @param postIs     提交的数据流
     * @param headerMap  报文头
     * @param os         输出流
     * @param outTime    超时时间
     * @throws Exception 异常
     * 2015年11月16日
     * @author 马宝刚
     */
    public static void call(
            String             sUrl,
            InputStream        postIs,
            Map<String,String> headerMap,
            String             contentType,
            OutputStream       os,
            int                outTime) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[InputStream] contentType:["+contentType+"] outTime:["+outTime+"]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            if(outTime>0) {
                urlc.setConnectTimeout(outTime);
                urlc.setReadTimeout(outTime);
            }
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,contentType,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            int readCount; //读取字节
            if(postIs!=null) {
                //构建输出信息流
              OutputStream out = urlc.getOutputStream();
                while((readCount=postIs.read(readBytes))!=-1) {
                    //放入缓存
                  out.write(readBytes,0,readCount);
                }
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息
            is = urlc.getInputStream(); //获取信息流
            while ((readCount = is.read(readBytes))!=-1) {
                os.write(readBytes,0,readCount);
            }
        } catch (IOException e) {
          throw new HttpException(sUrl,"[InputStream Data]",statusCode,statusMsg,e);
        }finally {
            try {
              postIs.close();
            } catch (Exception e2) {}
            try {
                is.close();
            } catch (Exception e2) {}
            try {
                os.flush();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 调用URL并直接输出
     * @param sUrl       调用url
     * @param postIs     提交的数据流
     * @param headerMap  报文头
     * @param os         输出流
     * @param outTime    超时时间
     * @throws Exception 异常
     * 2015年11月16日
     * @author 马宝刚
     */
    public static void call(
            String             sUrl,
            InputStream        postIs,
            Map<String,String> headerMap,
            Map<String,String> customHeaderMap,
            String             contentType,
            OutputStream       os,
            int                outTime) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[InputStream] contentType:["+contentType+"]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            if(outTime>0) {
                urlc.setConnectTimeout(outTime);
                urlc.setReadTimeout(outTime);
            }
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            
            if(customHeaderMap!=null) {
                //设置自定义头信息
                String[] keyList = BaseUtil.getMapKeys(customHeaderMap);
                String keyValue;
                for(int i=0;i<keyList.length;i++) {
                  keyValue = SString.valueOf(customHeaderMap.get(keyList[i]));
                  if(keyValue.length()<1) {
                    continue;
                  }
                  urlc.setRequestProperty(keyList[i],keyValue);
                }
            }
            setHeaderInfo(urlc,headerMap,contentType,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            int readCount; //读取字节
            if (postIs!=null) {
                //构建输出信息流
              OutputStream out = urlc.getOutputStream();
                while((readCount=postIs.read(readBytes))!=-1) {
                    //放入缓存
                  out.write(readBytes,0,readCount);
                }
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息

            //获取信息流
            is = urlc.getInputStream();
            while ((readCount = is.read(readBytes))!=-1) {
                os.write(readBytes,0,readCount);
            }
        } catch (IOException e) {
          throw new HttpException(sUrl,"[InputStream Data]",statusCode,statusMsg,e);
        }finally {
            try {
              postIs.close();
            } catch (Exception e2) {}
            try {
                is.close();
            } catch (Exception e2) {}
            try {
                os.flush();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 调用URL并直接输出
     * @param sUrl       调用url
     * @param postIs     提交的数据流
     * @param headerMap  报文头
     * @param os         输出流
     * @param outTime    超时时间
     * @throws Exception 异常
     * 2015年11月16日
     * @author 马宝刚
     */
    public static void call(
            String             sUrl,
            InputStream        postIs,
            Map<String,String> headerMap,
            Map<String,String> customHeaderMap,
            String             contentType,
            IResponse          resp,
            int                outTime) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        OutputStream os = null; //输出流
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] params:[InputStream] contentType:["+contentType+"]");
          }
          os = resp.getOutputStream();
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            if(outTime>0) {
                urlc.setConnectTimeout(outTime);
                urlc.setReadTimeout(outTime);
            }
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            
            if(customHeaderMap!=null) {
                //设置自定义头信息
                String[] keyList = BaseUtil.getMapKeys(customHeaderMap);
                String keyValue;
                for(int i=0;i<keyList.length;i++) {
                  keyValue = SString.valueOf(customHeaderMap.get(keyList[i]));
                  if(keyValue.length()<1) {
                    continue;
                  }
                  urlc.setRequestProperty(keyList[i],keyValue);
                }
            }
            setHeaderInfo(urlc,headerMap,contentType,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            
            //头主键
            String headerKey = "Content-Disposition";
            //头值
            String headerValue = SString.valueOf(headerMap.remove(headerKey));
            if(headerValue.length()>0) {
              resp.setHeader(headerKey,headerValue);
            }
            //代理程序设置长度并不是真正输出的长度，会导致空白页面 或者内容显示不全（反复测试过）
            //headerKey = "Content-Length";
            //headerValue = SString.valueOf(headerMap.remove(headerKey));
            //if(headerValue.length()>0) {
            //  resp.setContentLength(SInteger.valueOf(headerValue));
            //}
            headerKey = "Content-Type";
            headerValue = SString.valueOf(headerMap.remove(headerKey));
            if(headerValue.length()>0) {
              resp.setContentType(headerValue);
            }
            headerKey = "Content-Encoding";
            headerValue = SString.valueOf(headerMap.remove(headerKey));
            if(headerValue.length()>0) {
              resp.setHeader(headerKey,headerValue);
            }
            headerKey = "Content-Language";
            headerValue = SString.valueOf(headerMap.remove(headerKey));
            if(headerValue.length()>0) {
              resp.setHeader(headerKey,headerValue);
            }
            int readCount; //读取字节
            if (postIs!=null) {
                //构建输出信息流
              OutputStream out = urlc.getOutputStream();
                while((readCount=postIs.read(readBytes))!=-1) {
                    //放入缓存
                  out.write(readBytes,0,readCount);
                }
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息

            //获取信息流
            is = urlc.getInputStream();
            while ((readCount = is.read(readBytes))!=-1) {
                os.write(readBytes,0,readCount);
            }
        } catch (IOException e) {
          throw new HttpException(sUrl,"[InputStream Data]",statusCode,statusMsg,e);
        }finally {
            try {
              postIs.close();
            } catch (Exception e2) {}
            try {
                is.close();
            } catch (Exception e2) {}
            try {
                os.flush();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 调用URL并直接输出
     * @param sUrl       调用url
     * @param dataMap    提交的参数
     * @param headerMap  报文头
     * @param os         输出流
     * @param outTime    超时时间
     * @throws Exception 异常
     * 2015年11月16日
     * @author 马宝刚
     */
    public static void call(
            String             sUrl,
            Map<String,?>      dataMap,
            Map<String,String> headerMap,
            OutputStream       os,
            int                outTime) throws Exception {
        if(headerMap==null) {
            headerMap = new HashMap<String,String>();
        }
        InputStream is = null; //读入信息流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
          //尝试从线程容器中获取日志对象
          ILog log = (ILog)ThreadSession.get("log");
          if(log!=null) {
            log.log("Begin Call Url:["+sUrl+"] outTime:["+outTime+"] params:["+DebugUtil.getMapValue(dataMap)+"]");
          }
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            URLConnection urlc = url.openConnection();
            if(outTime>0) {
                urlc.setConnectTimeout(outTime);
                urlc.setReadTimeout(outTime);
            }
            if(urlc instanceof HttpsURLConnection) {
                //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
                ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
                ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
            }
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,headerMap,null,0,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if(dataMap!=null && dataMap.size()>0) {
                //构建输出信息流
                DataOutputStream out =  new DataOutputStream(urlc.getOutputStream()); 
                out.writeBytes(StringUtil.getParaStringFromMapPro(dataMap));
            }
            statusCode = ((HttpURLConnection)urlc).getResponseCode();
            statusMsg = ((HttpURLConnection)urlc).getResponseMessage();
            fixHeadInfo(urlc,headerMap); //处理 头信息

            //获取信息流
            is = urlc.getInputStream();
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
                os.write(readBytes,0,readCount);
            }
        } catch (IOException e) {
          throw new HttpException(sUrl,StringUtil.getParaStringFromMapPro(dataMap),statusCode,statusMsg,e);
        }finally {
            try {
                is.close();
            } catch (Exception e2) {}
            try {
                os.flush();
            }catch(Exception e2) {}
        }
    }
    
    /**
     * 发送附带数字证书的请求
     * @param sUrl         目标url
     * @param params       提交参数
     * @param certFile     证书文件对象
     * @param pwd          证书密码
     * @param sendEncoding 发送编码
     * @param resEncoding  返回信息编码
     * @param contentType  提交请求内容格式 0html  1xml  2json
     * @return             调用结果
     * @throws Exception   异常
     * 2016年1月29日
     * @author 马宝刚
     */
    public static String sslCall(
                String sUrl,
                String params,
                File   certFile,
                String pwd,
                String sendEncoding,
                String resEncoding,
                int    contentType) throws Exception {
        //证书存储类
        KeyStore keyStore  = KeyStore.getInstance("PKCS12");
        //证书读入流
        FileInputStream instream = new FileInputStream(certFile);
        try {
            keyStore.load(instream, pwd.toCharArray());
        } finally {
            instream.close();
        }
        //构建密钥管理类
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());  
        kmf.init(keyStore, pwd.toCharArray());
        //信任全部证书（必须的，证书都指定好了，还轮不到破代码不信任
        TrustManager[] trustAllCerts = new TrustManager[] { 
                new X509TrustManager() {
                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                                return new java.security.cert.X509Certificate[] {};
                        }
                        @Override
                        public void checkClientTrusted(X509Certificate[] chain, String authType)
                                throws CertificateException {
                        }
                        @Override
                        public void checkServerTrusted(X509Certificate[] chain, String authType)
                                throws CertificateException {
                        }
                }
        };
        
        //构建证书管理类
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
        tmf.init(keyStore);  
        //构建上下文
        SSLContext sslContext = SSLContext.getInstance("TLSv1"); 
        
        //初始化上下文
        sslContext.init(kmf.getKeyManagers(),trustAllCerts, SecureRandom.getInstance("SHA1PRNG")); 
        
        InputStream is = null; //读入信息流
        ByteArrayOutputStream bos = null; //字节输出流
        byte[] readBytes = new byte[BUFFER_SIZE]; //读入缓存
        int statusCode = 0; //返回状态码
        String statusMsg = ""; //返回状态信息
        try {
            //构造URL
            URL url = new URL(sUrl);
            //构造链接
            HttpsURLConnection urlc = (HttpsURLConnection)url.openConnection();
            
            //超时时间
            int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
            if(timeOut>0) {
              //设置链接超时时间
              urlc.setConnectTimeout(timeOut);
            }
            timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
            if(timeOut>0) {
              //设置读取超时时间
              urlc.setReadTimeout(timeOut);
            }
            
            //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
            urlc.setHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
            urlc.setSSLSocketFactory(sslContext.getSocketFactory());
        
            //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
            urlc.setInstanceFollowRedirects(false);
            setHeaderInfo(urlc,null,null,contentType,null); //设置头信息
            urlc.setDoOutput(true);   
            urlc.setUseCaches(false);
            if (params!=null && params.length()>0) {
                //构建输出信息流
                DataOutputStream out = 
                    new DataOutputStream(urlc.getOutputStream());   
                if(sendEncoding!=null && sendEncoding.length()>0) {
                    out.write(params.getBytes(sendEncoding));
                }else {
                    out.writeBytes(params);
                }
                out.flush();
                out.close();   
            }
            statusCode = urlc.getResponseCode();
            statusMsg = urlc.getResponseMessage();

            //获取信息流
            is = urlc.getInputStream();
            //建立字节输出流
            bos = new ByteArrayOutputStream();
            int readCount; //读取字节
            while ((readCount = is.read(readBytes))!=-1) {
                bos.write(readBytes,0,readCount);
            }
        } catch (IOException e) {
          throw new HttpException(sUrl,params,statusCode,statusMsg,e);
        }finally {
            try {
                is.close();
            } catch (Exception e2) {}
        }
        if(resEncoding!=null && resEncoding.length()>0) {
            return bos.toString(resEncoding);
        }
        return bos.toString();
    }
    
    /**
     * 交换会话主键值
     * 
     * 通常用在用户请求到后台，后台再访问其它系统
     * 
     * isSetCookie为true时，用在调用目标系统后返回的信息，如果包含了srcKey（通常为 JSESSIONID），则将主键名替换成objKey
     * 这样就不会影响用户当前请求的会话主键值了，并且将当前系统的会话主键值，与目标系统的会话主键值都输出到用户客户端中。
     * 
     * isSetCookie为false时，在请求目标系统时，会将objKey值替换srcKey值，这样就实现了目标系统的会话信息同步
     * 
     * @param cookieInfo   原Cookie信息值
     * @param srcKey       原会话主键名
     * @param objKey       目标会话主键名
     * @param isSetCookie  是否为调用目标系统后，返回的会话信息
     * @return             处理后的Cookie信息值
     * 2020年5月25日
     * @author MBG
     */
    private static String swapCookie(String cookieInfo,String srcKey,String objKey,boolean isSetCookie) {
      if(cookieInfo==null || srcKey==null || srcKey.length()<1 || objKey==null || srcKey.length()<1) {
        return "";
      }
      int    point; //分隔符位置
    //头部值序列  
    /*
     * 服务器返回Cookie时，可能会返回多个，比如：
     * Set-Cookie: JSESSIONID=69FA0483FD80E8E4B4A85352B6FAB8BB; Path=/app/; HttpOnly
     * Set-Cookie: PXSESSIONID=PX-14295943421520-938; Path=/app
     * Set-Cookie: PXSERVERID=app_server3; Path=/app
     * 
     * 但是我们发送的Cookie格式并不是返回的这种格式，而是这种格式
     * Cookie: PXSESSIONID=PX-14295943421520-938; PXSERVERID=app_server1; JSESSIONID=PX1429753309470-14297533094700-29
     */
      if(isSetCookie) {
        //Set-Cookie 一行只有一个值
        point = cookieInfo.indexOf("=");
        if(point<0) {
          return cookieInfo;
        }
        String key = cookieInfo.substring(0,point).trim();
        if(key.equalsIgnoreCase(srcKey)) {
          return objKey+cookieInfo.substring(point);
        }
        return cookieInfo;
      }
      //分隔为多个cookie值
      String[] infos = BaseUtil.split(cookieInfo,";");
      //Cookie容器
      SListMap<String> cookieMap = new SListMap<String>();
      for(int i=0;i<infos.length;i++) {
        infos[i] = infos[i].trim();
        point    = infos[i].indexOf("=");
        if(point<0) {
          continue;
        }
        cookieMap.put(infos[i].substring(0,point),infos[i].substring(point+1));
      }
      //移除当前系统cookie值
      cookieMap.remove(srcKey);
      String value = cookieMap.get(objKey); //会话主键值
      if(value!=null && value.length()>0) {
        cookieMap.put(srcKey,value);
      }
      cookieMap.remove(objKey);
      //构建返回值
      StringBuffer res = new StringBuffer();
      for(int i=0;i<cookieMap.size();i++) {
        if(i>0) {
          res.append("; ");
        }
        res.append(cookieMap.key(i)).append("=").append(cookieMap.value(i));
      }
      return res.toString();
    }

    /**
     * 调用目标URL
     * 
     * 这个方法通常用于： 外网通过当前系统访问内网中的一个系统，需要通过当前系统代理到内网系统
     * 
     * 以前的做法是将调用目标系统的头信息容器放到当前系统的会话中。采用这个方法后，可以将目标系统
     * 的会话主键直接返回到客户端，无需在当前系统保存头信息容器。另外这个方法也无需判断客户端的请求
     * 类型等各种情况，而是直接代理过去。
     * 
     * 有时候调用内网系统返回的信息需要修改，比如返回的html内容中的链接，需要修改成当前系统请求的链接
     * 可以通过参数不直接输出到页面，而是返回字符串
     * 
     * 
     * @param sUrl                URL地址
     * @param req                 请求对象
     * @param resp                反馈对象
     * @param localSessionKeyName 如果当前系统用的会话cookie主键名跟目标
     *                            系统重名，需要设置当前cookie名
     * @param agentSessionKeyName 在调用完目标系统后，将目标系统的会话
     *                            cookie主键名重命名为这个值，然后将这个
     *                            值直接返回到页面
     *                             
     * @param redirBaseurl        如果重定向，需要设置的根路径
     * @param returnText          是否将调用目标系统返回的信息作为返回值
     *                            准备做进一步处理（比如替换掉html内容中的
     *                            路径等等）
     * @return 调用目标系统返回的内容
     * @throws Exception   异常
     * 2019年9月10日
     * @author MBG
     */
  @SuppressWarnings("resource")
  public static String call(
        String              sUrl,
        HttpServletRequest  req,
        HttpServletResponse resp,
        String              localSessionKeyName,
        String              agentSessionKeyName,
        String              redirBaseurl,
        boolean             returnText) throws Exception {
      byte[]       readBytes  = new byte[BUFFER_SIZE]; //读入缓存
      int          readCount;                          //读取的字节数
      int          statusCode = 0;                     //返回状态码
      String       statusMsg  = "";                    //返回信息
      InputStream  is         = null;                  //读入流
      //尝试从线程容器中获取日志对象
      ILog log = (ILog)ThreadSession.get("log");
      if(log!=null) {
        log.log("Begin Call Url:["+sUrl+"]");
      }
      //构造URL
      URL url = new URL(sUrl);
      //构造链接
      URLConnection urlc = url.openConnection();
      //超时时间
      int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
      if(timeOut>0) {
        //设置链接超时时间
        urlc.setConnectTimeout(timeOut);
      }
      timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
      if(timeOut>0) {
        //设置读取超时时间
        urlc.setReadTimeout(timeOut);
      }
      if(urlc instanceof HttpsURLConnection) {
        //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
        ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
          @Override
                public boolean verify(String hostname, SSLSession session) {
            return true;
          }
        });
        ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
      }
      //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
      ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);

      /*
       * 设置请求报文头
       */
      //设置禁止缓存，否则可能会返回304，从缓存获取
      urlc.setRequestProperty("Cache-Control","no-cache");
      urlc.setRequestProperty("Pragma","no-cache");
      //设置头信息
      Enumeration<String> headerNames = req.getHeaderNames();
      String key;   //头主键
      String value; //头信息值
      while(headerNames.hasMoreElements()){
        key = headerNames.nextElement();
        if(key==null || "cache-control".equalsIgnoreCase(key) || "pragma".equalsIgnoreCase(key)) {
          continue;
        }
        value = req.getHeader(key);
        if(value.length()<1) {
          continue;
        }
        if(log!=null) {
          log.log("Request Header:["+key+"] value:["+value+"]");
        }
        if("cookie".equalsIgnoreCase(key)) {
          value = swapCookie(value,localSessionKeyName,agentSessionKeyName,false);
            if(log!=null) {
              log.log("Set Request Header:["+key+"] FixValue:["+value+"]");
            }
        }
        urlc.setRequestProperty(key,value);
      }
      //设置请求类型
      ((HttpURLConnection)urlc).setRequestMethod(req.getMethod());
      urlc.setDoOutput(true);   
      urlc.setUseCaches(false);
      if(SLong.valueOf(req.getHeader("Content-Length"))>0) {
        OutputStream dataOs = null; //提交数据流
        try {
          is     = req.getInputStream();
          dataOs = urlc.getOutputStream();
          while((readCount=is.read(readBytes))!=-1) {
            //放入缓存
            dataOs.write(readBytes,0,readCount);
          }
          dataOs.flush();
        }finally {
          try {
            is.close();
          }catch(Exception e2) {}
          try {
            dataOs.close();
          }catch(Exception e2) {}
        }
      }
      
      //设置返回值代码
      statusCode = ((HttpURLConnection)urlc).getResponseCode();
      statusMsg  = ((HttpURLConnection)urlc).getResponseMessage();
      
      if(log!=null) {
        log.log("Resonse Status:["+statusCode+"] Msg:["+statusMsg+"]");
      }
      
      /*
       * 设置返回报文头
       */
      int    point;       //分隔符位置
      String headerKey;   //头主键
      String headerValue; //头值
      //没有结束上限，在循环体中退出，大于索引数时不会抛错
      for(int i=0;;i++) {
        headerKey   = SString.valueOf(urlc.getHeaderFieldKey(i));
        headerValue = SString.valueOf(urlc.getHeaderField(i));
          
        if (headerKey.length()<1 && headerValue.length()<1) {
          //如果主键和值都为空，头结束
          break;
        }
        if (headerKey.length()<1 || headerValue.length()<1) {
            //主键和值其中一个为空，忽略
          continue;
        }
        if(log!=null) {
          log.log("Response Header:["+headerKey+"] value:["+headerValue+"]");
        }
        //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
        if(redirBaseurl!=null && redirBaseurl.length()>0 && 
          (statusCode==HttpURLConnection.HTTP_MOVED_TEMP 
            || statusCode==HttpURLConnection.HTTP_MOVED_PERM) 
                && "location".equalsIgnoreCase(headerKey)) {
          point = headerValue.indexOf("://");
              if(point>0) {
                headerValue = headerValue.substring(point+3);
                point = headerValue.indexOf("/");
                if(point>0) {
                  headerValue = headerValue.substring(point);
                }
              }
              headerValue = redirBaseurl+headerValue;
        }
        if("transfer-encoding".equalsIgnoreCase(headerKey)
            || "connection".equalsIgnoreCase(headerKey) 
            || "server".equalsIgnoreCase(headerKey) ) {
          continue;
        }
        if(returnText) {
          //需要对输出值做处理
          if( "content-encoding".equalsIgnoreCase(headerKey) 
              || "content-length".equalsIgnoreCase(headerKey)) {
            //因为返回的内容肯定是要做处理的，处理后的内容长度肯定根之前不同，所以过滤掉content-length
            continue;
          }
        }
        /*
         * 服务器返回Cookie时，可能会返回多个，比如：
         * Set-Cookie: JSESSIONID=69FA0483FD80E8E4B4A85352B6FAB8BB; Path=/app/; HttpOnly
         * Set-Cookie: PXSESSIONID=PX-14295943421520-938; Path=/app
         * Set-Cookie: PXSERVERID=app_server3; Path=/app
         * 
         * 但是我们发送的Cookie格式并不是返回的这种格式，而是这种格式
         * Cookie: PXSESSIONID=PX-14295943421520-938; PXSERVERID=app_server1; JSESSIONID=PX1429753309470-14297533094700-29
         */
        if("set-cookie".equalsIgnoreCase(headerKey)) {
          headerValue = swapCookie(headerValue,localSessionKeyName,agentSessionKeyName,true);
        }
        if(log!=null) {
          log.log("Set Response Header:["+headerKey+"] FixValue:["+headerValue+"]");
        }
        //这里不能用setHeader，因为比如Set-Cookie，需要设置多个同主键名的值
        resp.addHeader(headerKey,headerValue);
      }
      
      if(statusCode!=200) {
        resp.sendError(statusCode,statusMsg);
        return "";
      }
      if(returnText) {
        //构建输出流
        ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
        //构建缓存
        byte[] buffer = new byte[BUFFER_SIZE];
        try {
          is                = urlc.getInputStream();
        if("gzip".equals(urlc.getHeaderField("Content-Encoding"))) {
          is = new GZIPInputStream(is);
        }
          int     bytesRead = -1;
          while ((bytesRead = is.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
          }
          out.flush();
        }finally {
          try {
            is.close();
          }catch (IOException ex) {
            ex.printStackTrace();
          }
          try {
            out.close();
          }catch (IOException ex) {
            ex.printStackTrace();
          }
      }
      //通过返回内容类型返回编码格式
      String enc = getEncodingByContentType(urlc.getHeaderField("Content-Type"));
        return out.toString(enc);
      }
      long allCount   = 0; //写入字节数
      //直接输出到页面
      OutputStream os = null; //输出流
    try {
      is = urlc.getInputStream();
      os = resp.getOutputStream();
      /*
      //在报文头中也输出了返回的编码格式，所以不用解码
      if("gzip".equals(urlc.getHeaderField("Content-Encoding"))) {
        is = new GZIPInputStream(is);
      }
      */
      while ((readCount = is.read(readBytes))!=-1) {
        allCount += readCount;
        os.write(readBytes,0,readCount);
      }
      os.flush();
    }finally {
      try {
        is.close();
      }catch(Exception e2) {}
      try {
        os.close();
      }catch(Exception e2) {}
    }
      if(log!=null) {
        log.log("Call Url:["+sUrl+"] Over Send Length:["+allCount+"].......7");
      }
    return null;
    }
  
  /**
   * 处理成标准的报文头主键
   * 从req中获取的报文头主键都是小写的，但是请求到目标中的报文头主键首字母需要大写
   * @param key  待处理报文头主键
   * @return     处理后报文头主键
   * 2020年7月18日
   * @author MBG
   */
  public static String fixHeaderKey(String key) {
    if(key==null || key.length()<1) {
      return "";
    }
    //构造返回值
    StringBuffer sbf = new StringBuffer();
    //拆分单词
    String[] keys = key.split("-");
    for(int i=0;i<keys.length;i++) {
      if(i>0) {
        sbf.append("-");
      }
      if(keys[i].length()>0) {
        sbf.append(keys[i].substring(0,1).toUpperCase());
      }
      if(keys[i].length()>1) {
        sbf.append(keys[i].substring(1));
      }
    }
    return sbf.toString();
  }
  
    /**
     * 调用目标URL
     * 
     * 这个方法通常用于： 外网通过当前系统访问内网中的一个系统，需要通过当前系统代理到内网系统
     * 
     * @param  sUrl           URL地址
     * @param  req            请求对象
     * @param  resp           反馈对象
     * @param  headerMap      调用目标后返回的头信息容器（提交数据时不采用这里的信息，仅作返回时保存头信息）
     * @param  objSessionInfo 目标系统会话主键，在请求目标系统时，不会采用req中的会话主键信息，而用这个变量值替代
     * @param  redirBaseurl   如果重定向，需要设置的根路径
     * @param  returnText     该值为false时，调用结果直接输出到resp中（并不会将调用目标会话主键设置到resp头中）,为true时返回请求代码
     * @return                returnText为true时返回请求代码
     * @throws Exception      异常
     * 2019年9月10日
     * @author MBG
     */
  @SuppressWarnings("resource")
  public static String call(
        String              sUrl,
        HttpServletRequest  req,
        HttpServletResponse resp,
        Map<String,String>  headerMap,
        String              objSessionInfo,
        String              redirBaseurl,
        boolean             returnText) throws Exception {
      byte[]       readBytes  = new byte[BUFFER_SIZE]; //读入缓存
      int          readCount;                          //读取的字节数
      int          statusCode = 0;                     //返回状态码
      String       statusMsg  = "";                    //返回信息
      InputStream  is         = null;                  //读入流
      //尝试从线程容器中获取日志对象
      ILog log = (ILog)ThreadSession.get("log");
      if(log!=null) {
        log.log("Begin Call Url:["+sUrl+"]");
      }
      //构造URL
      URL url = new URL(sUrl);
      //构造链接
      URLConnection urlc = url.openConnection();
      //超时时间
      int timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_connect_time_out"));
      if(timeOut>0) {
        //设置链接超时时间
        urlc.setConnectTimeout(timeOut);
      }
      timeOut = SInteger.valueOf(ThreadSession.get("_httpcall_read_time_out"));
      if(timeOut>0) {
        //设置读取超时时间
        urlc.setReadTimeout(timeOut);
      }
      if(urlc instanceof HttpsURLConnection) {
        //通常调用的路径都是内部指定的，非得验证域名，导致域名验证失败
        ((HttpsURLConnection)urlc).setHostnameVerifier(new HostnameVerifier() {
          @Override
                public boolean verify(String hostname, SSLSession session) {
            return true;
          }
        });
        ((HttpsURLConnection)urlc).setSSLSocketFactory(createSSLContext().getSocketFactory());
      }
      //设置不自动跳转获取数据（因为自动跳转时不会自动将cookie信息带过去，会导致session丢失）
      ((HttpURLConnection)urlc).setInstanceFollowRedirects(false);

      /*
       * 设置请求报文头
       */
      //设置禁止缓存，否则可能会返回304，从缓存获取
      urlc.setRequestProperty("Cache-Control","no-cache");
      urlc.setRequestProperty("Pragma","no-cache");
      //设置头信息
      Enumeration<String> headerNames = req.getHeaderNames();
      String key;   //头主键
      String value; //头信息值
      while(headerNames.hasMoreElements()){
        key = headerNames.nextElement();
        if(key==null || "cache-control".equalsIgnoreCase(key) 
            || "pragma".equalsIgnoreCase(key)) {
          continue;
        }
        value = req.getHeader(key);
        if(value.length()<1) {
          continue;
        }
        if("cookie".equalsIgnoreCase(key)) {
          value = objSessionInfo;
        }
        
        //处理成标准的报文头主键
        key = fixHeaderKey(key);
        
        if(log!=null) {
          log.log("Request Header:["+key+"] value:["+value+"]");
        }
        urlc.setRequestProperty(key,value);
      }
      //设置请求类型
      ((HttpURLConnection)urlc).setRequestMethod(req.getMethod());
      urlc.setDoOutput(true);   
      urlc.setUseCaches(false);
      if(SLong.valueOf(req.getHeader("Content-Length"))>0) {
        OutputStream dataOs = null; //提交数据流
        int allCount = 0;
        try {
          is     = req.getInputStream();
          dataOs = urlc.getOutputStream();
          while((readCount=is.read(readBytes))!=-1) {
            //放入缓存
            dataOs.write(readBytes,0,readCount);
            allCount += readCount;
          }
          dataOs.flush();
        }finally {
          try {
            is.close();
          }catch(Exception e2) {}
          try {
            dataOs.close();
          }catch(Exception e2) {}
        }
        if(log!=null) {
          log.log("--------------send Request Data Count:["+allCount+"]");
        }
      }
      
      //设置返回值代码
      statusCode = ((HttpURLConnection)urlc).getResponseCode();
      statusMsg  = ((HttpURLConnection)urlc).getResponseMessage();
      
      /*
       * 设置返回报文头
       */
      String  headerKey;                 //头主键
      String  headerValue;               //头值
      boolean hasSetContentType = false; //是否已经设置过ContentType
      //没有结束上限，在循环体中退出，大于索引数时不会抛错
      for(int i=0;;i++) {
        headerKey   = SString.valueOf(urlc.getHeaderFieldKey(i));
        headerValue = SString.valueOf(urlc.getHeaderField(i));
        if (headerKey.length()<1 && headerValue.length()<1) {
          //如果主键和值都为空，头结束
          break;
        }
        //如果服务器端返回重定向信息，则自动跳转到重定向页面并且将cookie信息也带过去（内置方法不会带cookie过去，导致会话丢失）
        if(redirBaseurl!=null && redirBaseurl.length()>0 && 
          (statusCode==HttpURLConnection.HTTP_MOVED_TEMP 
            || statusCode==HttpURLConnection.HTTP_MOVED_PERM) 
                && "location".equalsIgnoreCase(headerKey)) {
          int point = headerValue.indexOf("://");
              if(point>0) {
                headerValue = headerValue.substring(point+3);
                point = headerValue.indexOf("/");
                if(point>0) {
                  headerValue = headerValue.substring(point);
                }
              }
              headerValue = redirBaseurl+headerValue;
        }
        if(headerMap!=null) {
          headerMap.put(headerKey,headerValue);
        }
        if (headerKey.length()<1 
            || "connection".equalsIgnoreCase(headerKey) 
            || "server".equalsIgnoreCase(headerKey) 
            || "transfer-encoding".equalsIgnoreCase(headerKey) 
            || "set-cookie".equalsIgnoreCase(headerKey) 
            || headerValue.length()<1) {
            //主键和值其中一个为空，忽略，或者是不需要输出的头信息
          //内部调用目标系统返回的sessin信息不返回到外部
          //transfer-encoding用了这个，导致获取到的信息返回到页面时报错
          continue;
        }
        if(returnText) {
          //需要对输出值做处理
          if("content-encoding".equalsIgnoreCase(headerKey) 
              || "content-length".equalsIgnoreCase(headerKey)) {
            //因为返回的内容肯定是要做处理的，处理后的内容长度肯定根之前不同，所以过滤掉content-length
            continue;
          }
        }
        if(log!=null) {
          log.log("Response Header:["+headerKey+"] value:["+headerValue+"]");
        }
        
        //有时候目标服务返回的下载文件类型是错误的，导致手机端显示错误，可以通过头参数 
        //强制更正文件类型值
        if(headerMap!=null 
            && SBoolean.valueOf(headerMap.get("_Fix_Download_ContentType")) 
            && "content-disposition".equalsIgnoreCase(headerKey)) {
          //通过文件扩展名，设置Content-Type
          int point = headerValue.lastIndexOf(".");
          if(point>0) {
            //下载文件的扩展名
            String extName = headerValue.substring(point+1);
            resp.setHeader("Content-Type",getMime(extName));
            hasSetContentType = true;
            headerMap.put("Content-Type_fixed",getMime(extName));
          }
        }
        if(!hasSetContentType) {
          //这里不需要设置Set-Cookie，不用addHeader
          resp.setHeader(headerKey,headerValue);
        }
      }
      if(statusCode!=200) {
        resp.sendError(statusCode,statusMsg);
        return "";
      }
      int outSize = 0; //输出字节数
      if(returnText) {
        //构建输出流
        ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
        //构建缓存
        byte[] buffer = new byte[BUFFER_SIZE];
        try {
          is                = urlc.getInputStream();
        if("gzip".equals(urlc.getHeaderField("Content-Encoding"))) {
          is = new GZIPInputStream(is);
        }
          int     bytesRead = -1;
          while ((bytesRead = is.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
            outSize += bytesRead;
          }
          out.flush();
        }finally {
          try {
            is.close();
          }catch (IOException ex) {
            ex.printStackTrace();
          }
          try {
            out.close();
          }catch (IOException ex) {
            ex.printStackTrace();
          }
        }
        if(log!=null) {
          log.log("--------------------------Read Object URL:["+sUrl+"] Size:["+outSize+"]");
      }
      //通过返回内容类型返回编码格式
      String enc = getEncodingByContentType(urlc.getHeaderField("Content-Type"));
        return out.toString(enc);
      }
      //直接输出到页面
      OutputStream os = null; //输出流
    try {
      is = urlc.getInputStream();
      os = resp.getOutputStream();
      /*
      //在报文头中也输出了返回的编码格式，所以不用解码
      if("gzip".equals(urlc.getHeaderField("Content-Encoding"))) {
        is = new GZIPInputStream(is);
      }
      */
      while((readCount = is.read(readBytes))!=-1) {
        os.write(readBytes,0,readCount);
        outSize += readCount;
      }
      os.flush();
    }finally {
      try {
        is.close();
      }catch(Exception e2) {}
      try {
        os.close();
      }catch(Exception e2) {}
    }
      if(log!=null) {
        log.log("Begin Call Url:["+sUrl+"] Over.......7  Read Size:["+outSize+"]");
      }
    return null;
    }
  
  /**
   * 通过扩展名，获取文件类型 
   * @param extName 扩展名
   * @return        文件类型 
   * 2016年11月17日
   * @author MBG
   */
  public static String getMime(String extName){
    extName = extName.toLowerCase();
    if("abs".equals(extName)){
      return "audio/x-mpeg";
    }else if("ai".equals(extName)){
      return "application/postscript";
    }else if("aif".equals(extName)){
      return "audio/x-aiff";
    }else if("aifc".equals(extName)){
      return "audio/x-aiff";
    }else if("aiff".equals(extName)){
      return "audio/x-aiff";
    }else if("aim".equals(extName)){
      return "application/x-aim";
    }else if("art".equals(extName)){
      return "image/x-jg";
    }else if("asf".equals(extName)){
      return "video/x-ms-asf";
    }else if("asx".equals(extName)){
      return "video/x-ms-asf";
    }else if("au".equals(extName)){
      return "audio/basic";
    }else if("avi".equals(extName)){
      return "video/x-msvideo";
    }else if("avx".equals(extName)){
      return "video/x-rad-screenplay";
    }else if("bcpio".equals(extName)){
      return "application/x-bcpio";
    }else if("bin".equals(extName)){
      return "application/octet-stream";
    }else if("xls".equals(extName)){
      return "application/vnd.ms-excel";
    }else if("ppt".equals(extName)){
      return "application/vnd.ms-powerpoint";
    }else if("pdf".equals(extName)){
      return "application/pdf";
    }else if("bmp".equals(extName)){
      return "image/bmp";
    }else if("body".equals(extName)){
      return "text/html";
    }else if("cdf".equals(extName)){
      return "application/x-cdf";
    }else if("cer".equals(extName)){
      return "application/x-x509-ca-cert";
    }else if("class".equals(extName)){
      return "application/java";
    }else if("cpio".equals(extName)){
      return "application/x-cpio";
    }else if("csh".equals(extName)){
      return "application/x-csh";
    }else if("css".equals(extName)){
      return "text/css";
    }else if("dib".equals(extName)){
      return "image/bmp";
    }else if("doc".equals(extName)){
      return "application/msword";
    }else if("dtd".equals(extName)){
      return "text/plain";
    }else if("dv".equals(extName)){
      return "video/x-dv";
    }else if("dvi".equals(extName)){
      return "application/x-dvi";
    }else if("eps".equals(extName)){
      return "application/postscript";
    }else if("etx".equals(extName)){
      return "text/x-setext";
    }else if("exe".equals(extName)){
      return "application/octet-stream";
    }else if("gif".equals(extName)){
      return "image/gif";
    }else if("gtar".equals(extName)){
      return "application/x-gtar";
    }else if("gz".equals(extName)){
      return "application/x-gzip";
    }else if("hdf".equals(extName)){
      return "application/x-hdf";
    }else if("hqx".equals(extName)){
      return "application/mac-binhex40";
    }else if("htc".equals(extName)){
      return "text/x-component";
    }else if("htm".equals(extName)){
      return "text/html";
    }else if("json".equals(extName)){
      return "application/json";
    }else if("html".equals(extName)){
      return "text/html";
    }else if("hqx".equals(extName)){
      return "application/mac-binhex40";
    }else if("ief".equals(extName)){
      return "image/ief";
    }else if("jad".equals(extName)){
      return "text/vnd.sun.j2me.app-descriptor";
    }else if("jar".equals(extName)){
      return "application/java-archive";
    }else if("java".equals(extName)){
      return "text/plain";
    }else if("jnlp".equals(extName)){
      return "application/x-java-jnlp-file";
    }else if("jpe".equals(extName)){
      return "image/jpeg";
    }else if("jpeg".equals(extName)){
      return "image/jpeg";
    }else if("jpg".equals(extName)){
      return "image/jpeg";
    }else if("js".equals(extName)){
      return "text/javascript";
    }else if("jsf".equals(extName)){
      return "text/plain";
    }else if("jspf".equals(extName)){
      return "text/plain";
    }else if("kar".equals(extName)){
      return "audio/x-midi";
    }else if("latex".equals(extName)){
      return "application/x-latex";
    }else if("m3u".equals(extName)){
      return "audio/x-mpegurl";
    }else if("mac".equals(extName)){
      return "image/x-macpaint";
    }else if("man".equals(extName)){
      return "application/x-troff-man";
    }else if("me".equals(extName)){
      return "application/x-troff-me";
    }else if("mid".equals(extName)){
      return "audio/x-midi";
    }else if("midi".equals(extName)){
      return "audio/x-midi";
    }else if("mif".equals(extName)){
      return "application/x-mif";
    }else if("mov".equals(extName)){
      return "video/quicktime";
    }else if("movie".equals(extName)){
      return "video/x-sgi-movie";
    }else if("mp1".equals(extName)){
      return "audio/x-mpeg";
    }else if("mp2".equals(extName)){
      return "audio/x-mpeg";
    }else if("mp3".equals(extName)){
      return "audio/x-mpeg";
    }else if("mpa".equals(extName)){
      return "audio/x-mpeg";
    }else if("mpe".equals(extName)){
      return "video/mpeg";
    }else if("mpeg".equals(extName)){
      return "video/mpeg";
    }else if("mpega".equals(extName)){
      return "audio/x-mpeg";
    }else if("mpg".equals(extName)){
      return "video/mpeg";
    }else if("mpv2".equals(extName)){
      return "video/mpeg2";
    }else if("ms".equals(extName)){
      return "application/x-wais-source";
    }else if("nc".equals(extName)){
      return "application/x-netcdf";
    }else if("oda".equals(extName)){
      return "application/oda";
    }else if("pbm".equals(extName)){
      return "image/x-portable-bitmap";
    }else if("pct".equals(extName)){
      return "image/pict";
    }else if("pgm".equals(extName)){
      return "image/x-portable-graymap";
    }else if("pic".equals(extName)){
      return "image/pict";
    }else if("pict".equals(extName)){
      return "image/pict";
    }else if("pls".equals(extName)){
      return "audio/x-scpls";
    }else if("png".equals(extName)){
      return "image/png";
    }else if("pnm".equals(extName)){
      return "image/x-portable-anymap";
    }else if("pnt".equals(extName)){
      return "image/x-macpaint";
    }else if("ppm".equals(extName)){
      return "image/x-portable-pixmap";
    }else if("ps".equals(extName)){
      return "application/postscript";
    }else if("psd".equals(extName)){
      return "image/x-photoshop";
    }else if("qt".equals(extName)){
      return "video/quicktime";
    }else if("qti".equals(extName)){
      return "image/x-quicktime";
    }else if("qtif".equals(extName)){
      return "image/x-quicktime";
    }else if("ras".equals(extName)){
      return "image/x-cmu-raster";
    }else if("rgb".equals(extName)){
      return "image/x-rgb";
    }else if("rm".equals(extName)){
      return "application/vnd.rn-realmedia";
    }else if("roff".equals(extName)){
      return "application/x-troff";
    }else if("rtf".equals(extName)){
      return "application/rtf";
    }else if("rtx".equals(extName)){
      return "text/richtext";
    }else if("sh".equals(extName)){
      return "application/x-sh";
    }else if("shar".equals(extName)){
      return "application/x-shar";
    }else if("smf".equals(extName)){
      return "audio/x-midi";
    }else if("sit".equals(extName)){
      return "application/x-stuffit";
    }else if("snd".equals(extName)){
      return "audio/basic";
    }else if("src".equals(extName)){
      return "application/x-wais-source";
    }else if("sv4cpio".equals(extName)){
      return "application/x-sv4cpio";
    }else if("sv4crc".equals(extName)){
      return "application/x-sv4crc";
    }else if("swf".equals(extName)){
      return "application/x-shockwave-flash";
    }else if("t".equals(extName)){
      return "application/x-troff";
    }else if("tar".equals(extName)){
      return "application/x-tar";
    }else if("tcl".equals(extName)){
      return "application/x-tcl";
    }else if("tex".equals(extName)){
      return "application/x-tex";
    }else if("texi".equals(extName)){
      return "application/x-texinfo";
    }else if("texinfo".equals(extName)){
      return "application/x-texinfo";
    }else if("tif".equals(extName)){
      return "image/tiff";
    }else if("tiff".equals(extName)){
      return "image/tiff";
    }else if("tr".equals(extName)){
      return "application/x-troff";
    }else if("tsv".equals(extName)){
      return "text/tab-separated-values";
    }else if("txt".equals(extName)){
      return "text/plain";
    }else if("ulw".equals(extName)){
      return "audio/basic";
    }else if("ustar".equals(extName)){
      return "application/x-ustar";
    }else if("xbm".equals(extName)){
      return "image/x-xbitmap";
    }else if("xml".equals(extName)){
      return "text/xml";
    }else if("xpm".equals(extName)){
      return "image/x-xpixmap";
    }else if("xsl".equals(extName)){
      return "text/xml";
    }else if("xwd".equals(extName)){
      return "image/x-xwindowdump";
    }else if("wav".equals(extName)){
      return "audio/x-wav";
    }else if("svg".equals(extName)){
      return "image/svg+xml";
    }else if("svgz".equals(extName)){
      return "image/svg+xml";
    }else if("wbmp".equals(extName)){
      return "image/vnd.wap.wbmp";
    }else if("wml".equals(extName)){
      return "text/vnd.wap.wml";
    }else if("wmlc".equals(extName)){
      return "application/vnd.wap.wmlc";
    }else if("wmls".equals(extName)){
      return "text/vnd.wap.wmlscript";
    }else if("wmlscriptc".equals(extName)){
      return "application/vnd.wap.wmlscriptc";
    }else if("wrl".equals(extName)){
      return "x-world/x-vrml";
    }else if("Z".equals(extName)){
      return "application/x-compress";
    }else if("z".equals(extName)){
      return "application/x-compress";
    }else if("zip".equals(extName)){
      return "application/zip";
    }else if("dot".equals(extName)){
      return "application/msword";
    }else if("docx".equals(extName)){
      return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    }else if("dotx".equals(extName)){
      return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    }else if("docm".equals(extName)){
      return "application/vnd.ms-word.document.macroEnabled.12";
    }else if("dotm".equals(extName)){
      return "application/vnd.ms-word.template.macroEnabled.12";
    }else if("xlt".equals(extName)){
      return "application/vnd.ms-excel";
    }else if("xla".equals(extName)){
      return "application/vnd.ms-excel";
    }else if("xlsx".equals(extName)){
      return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    }else if("xltx".equals(extName)){
      return "application/vnd.openxmlformats-officedocument.spreadsheetml.template";
    }else if("xlsm".equals(extName)){
      return "application/vnd.ms-excel.sheet.macroEnabled.12";
    }else if("xltm".equals(extName)){
      return "application/vnd.ms-excel.template.macroEnabled.12";
    }else if("xlam".equals(extName)){
      return "application/vnd.ms-excel.addin.macroEnabled.12";
    }else if("xlsb".equals(extName)){
      return "application/vnd.ms-excel.sheet.binary.macroEnabled.12";
    }else if("pot".equals(extName)){
      return "application/vnd.ms-powerpoint";
    }else if("pps".equals(extName)){
      return "application/vnd.ms-powerpoint";
    }else if("ppa".equals(extName)){
      return "application/vnd.ms-powerpoint";
    }else if("pptx".equals(extName)){
      return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    }else if("potx".equals(extName)){
      return "application/vnd.openxmlformats-officedocument.presentationml.template";
    }else if("ppsx".equals(extName)){
      return "application/vnd.openxmlformats-officedocument.presentationml.slideshow";
    }else if("ppam".equals(extName)){
      return "application/vnd.ms-powerpoint.addin.macroEnabled.12";
    }else if("pptm".equals(extName)){
      return "application/vnd.ms-powerpoint.presentation.macroEnabled.12";
    }else if("potm".equals(extName)){
      return "application/vnd.ms-powerpoint.presentation.macroEnabled.12";
    }else if("ppsm".equals(extName)){
      return "application/vnd.ms-powerpoint.slideshow.macroEnabled.12";
    }
    return "application/octet-stream";
  }
  
  /**
   * 通过扩Content-Type，获取文件扩展名
   * @param contentType 内容类型
   * @return            文件扩展名
   * 2017年05月02日
   * @author MBG
   */
  public static String fixMime(String contentType){
      if(contentType==null) {
        return "unknown";
      }
      contentType = contentType.toLowerCase();
      if("audio/x-mpeg".equals(contentType)){
        return "abs";
      }else if("application/postscript".equals(contentType)){
        return "ai";
      }else if("audio/x-aiff".equals(contentType)){
        return "aif";
      }else if("application/x-aim".equals(contentType)){
        return "aim";
      }else if("image/x-jg".equals(contentType)){
        return "art";
      }else if("video/x-ms-asf".equals(contentType)){
        return "asf";
      }else if("video/x-msvideo".equals(contentType)){
        return "avi";
      }else if("video/x-rad-screenplay".equals(contentType)){
        return "avx";
      }else if("application/x-bcpio".equals(contentType)){
        return "bcpio";
      }else if("application/octet-stream".equals(contentType)){
        return "xls";
      }else if("image/bmp".equals(contentType)){
        return "bmp";
      }else if("text/html".equals(contentType)){
        return "html";
      }else if("application/x-cdf".equals(contentType)){
        return "cdf";
      }else if("application/x-x509-ca-cert".equals(contentType)){
        return "cer";
      }else if("application/java".equals(contentType)){
        return "class";
      }else if("application/x-cpio".equals(contentType)){
        return "cpio";
      }else if("application/x-csh".equals(contentType)){
        return "csh";
      }else if("text/css".equals(contentType)){
        return "css";
      }else if("application/msword".equals(contentType)){
        return "doc";
      }else if("video/x-dv".equals(contentType)){
        return "dv";
      }else if("application/x-dvi".equals(contentType)){
        return "dvi";
      }else if("text/x-setext".equals(contentType)){
        return "etx";
      }else if("image/gif".equals(contentType)){
        return "gif";
      }else if("application/x-gtar".equals(contentType)){
        return "gtar";
      }else if("application/x-gzip".equals(contentType)){
        return "gz";
      }else if("application/x-hdf".equals(contentType)){
        return "hdf";
      }else if("application/mac-binhex40".equals(contentType)){
        return "hqx";
      }else if("text/x-component".equals(contentType)){
        return "htc";
      }else if("image/ief".equals(contentType)){
        return "ief";
      }else if("text/vnd.sun.j2me.app-descriptor".equals(contentType)){
        return "jad";
      }else if("application/java-archive".equals(contentType)){
        return "jar";
      }else if("application/x-java-jnlp-file".equals(contentType)){
        return "jnlp";
      }else if("image/jpeg".equals(contentType)){
        return "jpg";
      }else if("text/javascript".equals(contentType)){
        return "js";
      }else if("audio/x-midi".equals(contentType)){
        return "midi";
      }else if("application/x-latex".equals(contentType)){
        return "latex";
      }else if("audio/x-mpegurl".equals(contentType)){
        return "m3u";
      }else if("image/x-macpaint".equals(contentType)){
        return "mac";
      }else if("application/x-troff-man".equals(contentType)){
        return "man";
      }else if("application/x-troff-me".equals(contentType)){
        return "me";
      }else if("application/x-mif".equals(contentType)){
        return "mif";
      }else if("video/quicktime".equals(contentType)){
        return "mov";
      }else if("video/x-sgi-movie".equals(contentType)){
        return "movie";
      }else if("video/mpeg".equals(contentType)){
        return "mpeg";
      }else if("application/x-wais-source".equals(contentType)){
        return "ms";
      }else if("application/x-netcdf".equals(contentType)){
        return "nc";
      }else if("application/oda".equals(contentType)){
        return "oda";
      }else if("image/x-portable-bitmap".equals(contentType)){
        return "pbm";
      }else if("application/pdf".equals(contentType)){
        return "pdf";
      }else if("image/x-portable-graymap".equals(contentType)){
        return "pgm";
      }else if("image/pict".equals(contentType)){
        return "pic";
      }else if("audio/x-scpls".equals(contentType)){
        return "pls";
      }else if("image/png".equals(contentType)){
        return "png";
      }else if("image/x-portable-anymap".equals(contentType)){
        return "pnm";
      }else if("image/x-portable-pixmap".equals(contentType)){
        return "ppm";
      }else if("image/x-photoshop".equals(contentType)){
        return "psd";
      }else if("image/x-quicktime".equals(contentType)){
        return "qti";
      }else if("image/x-cmu-raster".equals(contentType)){
        return "ras";
      }else if("image/x-rgb".equals(contentType)){
        return "rgb";
      }else if("application/vnd.rn-realmedia".equals(contentType)){
        return "rm";
      }else if("application/x-troff".equals(contentType)){
        return "roff";
      }else if("application/rtf".equals(contentType)){
        return "rtf";
      }else if("text/richtext".equals(contentType)){
        return "rtx";
      }else if("application/x-sh".equals(contentType)){
        return "sh";
      }else if("application/x-shar".equals(contentType)){
        return "shar";
      }else if("application/x-stuffit".equals(contentType)){
        return "sit";
      }else if("audio/basic".equals(contentType)){
        return "snd";
      }else if("application/x-sv4cpio".equals(contentType)){
        return "sv4cpio";
      }else if("application/x-sv4crc".equals(contentType)){
        return "sv4crc";
      }else if("application/x-shockwave-flash".equals(contentType)){
        return "swf";
      }else if("application/x-tar".equals(contentType)){
        return "tar";
      }else if("application/x-tcl".equals(contentType)){
        return "tcl";
      }else if("application/x-tex".equals(contentType)){
        return "tex";
      }else if("application/x-texinfo".equals(contentType)){
        return "texi";
      }else if("image/tiff".equals(contentType)){
        return "tiff";
      }else if("text/tab-separated-values".equals(contentType)){
        return "tsv";
      }else if("text/plain".equals(contentType)){
        return "txt";
      }else if("application/x-ustar".equals(contentType)){
        return "ustar";
      }else if("image/x-xbitmap".equals(contentType)){
        return "xbm";
      }else if("text/xml".equals(contentType)){
        return "xml";
      }else if("image/x-xpixmap".equals(contentType)){
        return "xpm";
      }else if("image/x-xwindowdump".equals(contentType)){
        return "xwd";
      }else if("audio/x-wav".equals(contentType)){
        return "wav";
      }else if("image/svg+xml".equals(contentType)){
        return "svg";
      }else if("image/vnd.wap.wbmp".equals(contentType)){
        return "wbmp";
      }else if("text/vnd.wap.wml".equals(contentType)){
        return "wml";
      }else if("application/vnd.wap.wmlc".equals(contentType)){
        return "wmlc";
      }else if("text/vnd.wap.wmlscript".equals(contentType)){
        return "wmls";
      }else if("application/vnd.wap.wmlscriptc".equals(contentType)){
        return "wmlscriptc";
      }else if("x-world/x-vrml".equals(contentType)){
        return "wrl";
      }else if("application/x-compress".equals(contentType)){
        return "z";
      }else if("application/zip".equals(contentType)){
        return "zip";
      }
      return "unknown";
    }
  
  /**
   * 通过扩展名，获取文件类型 0文件类型  1是否可以直接打开
   * @param extName 扩展名
   * @return        文件类型  1是否可以直接打开
   * 2016年11月17日
   * @author MBG
   */
  public static String[] getMimeInfo(String extName){
    extName = extName.toLowerCase();
    if("abs".equals(extName)){
      return new String[]{"audio/x-mpeg","0"};
    }else if("ai".equals(extName)){
      return new String[]{"application/postscript","0"};
    }else if("aif".equals(extName)){
      return new String[]{ "audio/x-aiff","1"};
    }else if("aifc".equals(extName)){
      return new String[]{"audio/x-aiff","1"};
    }else if("aiff".equals(extName)){
      return new String[]{"audio/x-aiff","1"};
    }else if("aim".equals(extName)){
      return new String[]{"application/x-aim","0"};
    }else if("art".equals(extName)){
      return new String[]{"image/x-jg","1"};
    }else if("asf".equals(extName)){
      return new String[]{"video/x-ms-asf","1"};
    }else if("asx".equals(extName)){
      return new String[]{"video/x-ms-asf","1"};
    }else if("au".equals(extName)){
      return new String[]{"audio/basic","1"};
    }else if("avi".equals(extName)){
      return new String[]{"video/x-msvideo","1"};
    }else if("avx".equals(extName)){
      return new String[]{"video/x-rad-screenplay","1"};
    }else if("bcpio".equals(extName)){
      return new String[]{"application/x-bcpio","0"};
    }else if("bin".equals(extName)){
      return new String[]{"application/octet-stream","0"};
    }else if("xls".equals(extName)){
      return new String[]{"application/octet-stream","0"};
    }else if("ppt".equals(extName)){
      return new String[]{"application/octet-stream","0"};
    }else if("pdf".equals(extName)){
      return new String[]{"application/octet-stream","0"};
    }else if("bmp".equals(extName)){
      return new String[]{"image/bmp","1"};
    }else if("body".equals(extName)){
      return new String[]{"text/html","1"};
    }else if("cdf".equals(extName)){
      return new String[]{"application/x-cdf","0"};
    }else if("cer".equals(extName)){
      return new String[]{"application/x-x509-ca-cert","0"};
    }else if("class".equals(extName)){
      return new String[]{"application/java","0"};
    }else if("cpio".equals(extName)){
      return new String[]{"application/x-cpio","0"};
    }else if("csh".equals(extName)){
      return new String[]{"application/x-csh","0"};
    }else if("css".equals(extName)){
      return new String[]{"text/css","0"};
    }else if("dib".equals(extName)){
      return new String[]{"image/bmp","1"};
    }else if("doc".equals(extName)){
      return new String[]{"application/msword","0"};
    }else if("dtd".equals(extName)){
      return new String[]{"text/plain","0"};
    }else if("dv".equals(extName)){
      return new String[]{"video/x-dv","1"};
    }else if("dvi".equals(extName)){
      return new String[]{"application/x-dvi","0"};
    }else if("eps".equals(extName)){
      return new String[]{"application/postscript","0"};
    }else if("etx".equals(extName)){
      return new String[]{"text/x-setext","0"};
    }else if("exe".equals(extName)){
      return new String[]{"application/octet-stream","0"};
    }else if("gif".equals(extName)){
      return new String[]{"image/gif","1"};
    }else if("gtar".equals(extName)){
      return new String[]{"application/x-gtar","0"};
    }else if("gz".equals(extName)){
      return new String[]{"application/x-gzip","0"};
    }else if("hdf".equals(extName)){
      return new String[]{"application/x-hdf","0"};
    }else if("hqx".equals(extName)){
      return new String[]{"application/mac-binhex40","0"};
    }else if("htc".equals(extName)){
      return new String[]{"text/x-component","0"};
    }else if("htm".equals(extName)){
      return new String[]{"text/html","1"};
    }else if("html".equals(extName)){
      return new String[]{"text/html","1"};
    }else if("hqx".equals(extName)){
      return new String[]{"application/mac-binhex40","0"};
    }else if("ief".equals(extName)){
      return new String[]{"image/ief","1"};
    }else if("jad".equals(extName)){
      return new String[]{"text/vnd.sun.j2me.app-descriptor","0"};
    }else if("jar".equals(extName)){
      return new String[]{"application/java-archive","0"};
    }else if("java".equals(extName)){
      return new String[]{"text/plain","0"};
    }else if("jnlp".equals(extName)){
      return new String[]{"application/x-java-jnlp-file","0"};
    }else if("jpe".equals(extName)){
      return new String[]{"image/jpeg","1"};
    }else if("jpeg".equals(extName)){
      return new String[]{"image/jpeg","1"};
    }else if("jpg".equals(extName)){
      return new String[]{"image/jpeg","1"};
    }else if("js".equals(extName)){
      return new String[]{"text/javascript","0"};
    }else if("jsf".equals(extName)){
      return new String[]{"text/plain","0"};
    }else if("jspf".equals(extName)){
      return new String[]{"text/plain","0"};
    }else if("kar".equals(extName)){
      return new String[]{"audio/x-midi","1"};
    }else if("latex".equals(extName)){
      return new String[]{"application/x-latex","0"};
    }else if("m3u".equals(extName)){
      return new String[]{"audio/x-mpegurl","0"};
    }else if("mac".equals(extName)){
      return new String[]{"image/x-macpaint","0"};
    }else if("man".equals(extName)){
      return new String[]{"application/x-troff-man","0"};
    }else if("me".equals(extName)){
      return new String[]{"application/x-troff-me","0"};
    }else if("mid".equals(extName)){
      return new String[]{"audio/x-midi","1"};
    }else if("midi".equals(extName)){
      return new String[]{"audio/x-midi","1"};
    }else if("mif".equals(extName)){
      return new String[]{"application/x-mif","0"};
    }else if("mov".equals(extName)){
      return new String[]{"video/quicktime","1"};
    }else if("movie".equals(extName)){
      return new String[]{"video/x-sgi-movie","1"};
    }else if("mp1".equals(extName)){
      return new String[]{"audio/x-mpeg","1"};
    }else if("mp2".equals(extName)){
      return new String[]{"audio/x-mpeg","1"};
    }else if("mp3".equals(extName)){
      return new String[]{"audio/x-mpeg","1"};
    }else if("mpa".equals(extName)){
      return new String[]{"audio/x-mpeg","1"};
    }else if("mpe".equals(extName)){
      return new String[]{"video/mpeg","1"};
    }else if("mpeg".equals(extName)){
      return new String[]{"video/mpeg","1"};
    }else if("mpega".equals(extName)){
      return new String[]{"audio/x-mpeg","1"};
    }else if("mpg".equals(extName)){
      return new String[]{"video/mpeg","1"};
    }else if("mpv2".equals(extName)){
      return new String[]{"video/mpeg2","1"};
    }else if("ms".equals(extName)){
      return new String[]{"application/x-wais-source","0"};
    }else if("nc".equals(extName)){
      return new String[]{"application/x-netcdf","0"};
    }else if("oda".equals(extName)){
      return new String[]{"application/oda","0"};
    }else if("pbm".equals(extName)){
      return new String[]{"image/x-portable-bitmap","0"};
    }else if("pct".equals(extName)){
      return new String[]{"image/pict","0"};
    }else if("pdf".equals(extName)){
      return new String[]{"application/pdf","0"};
    }else if("pgm".equals(extName)){
      return new String[]{"image/x-portable-graymap","0"};
    }else if("pic".equals(extName)){
      return new String[]{"image/pict","1"};
    }else if("pict".equals(extName)){
      return new String[]{"image/pict","1"};
    }else if("pls".equals(extName)){
      return new String[]{"audio/x-scpls","1"};
    }else if("png".equals(extName)){
      return new String[]{"image/png","1"};
    }else if("pnm".equals(extName)){
      return new String[]{"image/x-portable-anymap","0"};
    }else if("pnt".equals(extName)){
      return new String[]{"image/x-macpaint","0"};
    }else if("ppm".equals(extName)){
      return new String[]{"image/x-portable-pixmap","0"};
    }else if("ps".equals(extName)){
      return new String[]{"application/postscript","0"};
    }else if("psd".equals(extName)){
      return new String[]{"image/x-photoshop","0"};
    }else if("qt".equals(extName)){
      return new String[]{"video/quicktime","1"};
    }else if("qti".equals(extName)){
      return new String[]{"image/x-quicktime","1"};
    }else if("qtif".equals(extName)){
      return new String[]{"image/x-quicktime","1"};
    }else if("ras".equals(extName)){
      return new String[]{"image/x-cmu-raster","0"};
    }else if("rgb".equals(extName)){
      return new String[]{"image/x-rgb","0"};
    }else if("rm".equals(extName)){
      return new String[]{"application/vnd.rn-realmedia","0"};
    }else if("roff".equals(extName)){
      return new String[]{"application/x-troff","0"};
    }else if("rtf".equals(extName)){
      return new String[]{"application/rtf","0"};
    }else if("rtx".equals(extName)){
      return new String[]{"text/richtext","0"};
    }else if("sh".equals(extName)){
      return new String[]{"application/x-sh","0"};
    }else if("shar".equals(extName)){
      return new String[]{"application/x-shar","0"};
    }else if("smf".equals(extName)){
      return new String[]{"audio/x-midi","1"};
    }else if("sit".equals(extName)){
      return new String[]{"application/x-stuffit","0"};
    }else if("snd".equals(extName)){
      return new String[]{"audio/basic","1"};
    }else if("src".equals(extName)){
      return new String[]{"application/x-wais-source","0"};
    }else if("sv4cpio".equals(extName)){
      return new String[]{"application/x-sv4cpio","0"};
    }else if("sv4crc".equals(extName)){
      return new String[]{"application/x-sv4crc","0"};
    }else if("swf".equals(extName)){
      return new String[]{"application/x-shockwave-flash","1"};
    }else if("t".equals(extName)){
      return new String[]{"application/x-troff","0"};
    }else if("tar".equals(extName)){
      return new String[]{"application/x-tar","0"};
    }else if("tcl".equals(extName)){
      return new String[]{"application/x-tcl","0"};
    }else if("tex".equals(extName)){
      return new String[]{"application/x-tex","0"};
    }else if("texi".equals(extName)){
      return new String[]{"application/x-texinfo","0"};
    }else if("texinfo".equals(extName)){
      return new String[]{"application/x-texinfo","0"};
    }else if("tif".equals(extName)){
      return new String[]{"image/tiff","0"};
    }else if("tiff".equals(extName)){
      return new String[]{"image/tiff","0"};
    }else if("tr".equals(extName)){
      return new String[]{"application/x-troff","0"};
    }else if("tsv".equals(extName)){
      return new String[]{"text/tab-separated-values","0"};
    }else if("txt".equals(extName)){
      return new String[]{"text/plain","0"};
    }else if("ulw".equals(extName)){
      return new String[]{"audio/basic","0"};
    }else if("ustar".equals(extName)){
      return new String[]{"application/x-ustar","0"};
    }else if("xbm".equals(extName)){
      return new String[]{"image/x-xbitmap","0"};
    }else if("xml".equals(extName)){
      return new String[]{"text/xml","0"};
    }else if("xpm".equals(extName)){
      return new String[]{"image/x-xpixmap","0"};
    }else if("xsl".equals(extName)){
      return new String[]{"text/xml","0"};
    }else if("xwd".equals(extName)){
      return new String[]{"image/x-xwindowdump","0"};
    }else if("wav".equals(extName)){
      return new String[]{"audio/x-wav","1"};
    }else if("svg".equals(extName)){
      return new String[]{"image/svg+xml","0"};
    }else if("svgz".equals(extName)){
      return new String[]{"image/svg+xml","0"};
    }else if("wbmp".equals(extName)){
      return new String[]{"image/vnd.wap.wbmp","0"};
    }else if("wml".equals(extName)){
      return new String[]{"text/vnd.wap.wml","0"};
    }else if("wmlc".equals(extName)){
      return new String[]{"application/vnd.wap.wmlc","0"};
    }else if("wmls".equals(extName)){
      return new String[]{"text/vnd.wap.wmlscript","0"};
    }else if("wmlscriptc".equals(extName)){
      return new String[]{"application/vnd.wap.wmlscriptc","0"};
    }else if("wrl".equals(extName)){
      return new String[]{"x-world/x-vrml","0"};
    }else if("Z".equals(extName)){
      return new String[]{"application/x-compress","0"};
    }else if("z".equals(extName)){
      return new String[]{"application/x-compress","0"};
    }else if("zip".equals(extName)){
      return new String[]{"application/zip","0"};
    }
    return new String[]{"","0"};
  }

  /**
   * 通过返回信息类型返回其中包含的编码值
   * @param type 类型值
   * @return     编码值
   */
  public static String getEncodingByContentType(String type) {
    String res = "UTF-8"; // 构建返回值
    if (type == null) {
      return res;
    }
    //分隔符
    int point = type.toLowerCase().lastIndexOf("charset=");
    if (point > 0) {
      res = type.substring(point + 8);
    }
    point = res.indexOf(";");
    if (point > 0) {
      res = res.substring(0, point);
    }
    return res.trim();
  }
}